<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Sinclair QL planet</title>
    <link></link>
    <description>Sinclair QL planet</description>
    <atom:link href="/feed.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Mon, 14 Sep 2026 17:35:05 +0000</lastBuildDate>

    
    <item>
      <title>Q-emuLator 4.1</title>
      <link>http://qemulator.blogspot.com/2026/09/q-emulator-41.html</link>
      <guid isPermaLink="true">http://qemulator.blogspot.com/2026/09/q-emulator-41.html</guid>
      <pubDate>Sun, 13 Sep 2026 23:27:08 -0000</pubDate>
      <description><![CDATA[<p>&nbsp;Q-emuLator 4.1 has a new GPU graphics engine that uses less CPU and adds optional scanline and monitor persistence effects.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgKoSsh-egpyjJIik_HLzySTzMxjqDUNzZIb-wPPb57zKv2nz82cd1OqVOVUfbJh4D4fAGUDAuxisVetoqSpe6iDruoR0uVuVschub59ccTqZKslI3xR_lVGJsM8sSc2VQC03Kq4nxDcWHVNbGVG9A1azy9plWf30OdzuM_DZypHDdzmp4PKu_9FjCR70G6" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="430" data-original-width="465" height="303" src="https://blogger.googleusercontent.com/img/a/AVvXsEgKoSsh-egpyjJIik_HLzySTzMxjqDUNzZIb-wPPb57zKv2nz82cd1OqVOVUfbJh4D4fAGUDAuxisVetoqSpe6iDruoR0uVuVschub59ccTqZKslI3xR_lVGJsM8sSc2VQC03Kq4nxDcWHVNbGVG9A1azy9plWf30OdzuM_DZypHDdzmp4PKu_9FjCR70G6=w327-h303" width="327" /></a></div><br />Variable pixel ratio - a feature requested by many users - is finally available. It allows&nbsp; MODE 4 pixels to appear square at the expense of a squished image.<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhtjsPNpPvJzA5pNBZttlTX6dhJlcBI5oUb_S4YrYV8_UGH0dZk6RrQ_gQZDQ5QnoHsDOhuXrb6LWXYmJOcZprkpw-qTY5gl2v3ulgAYzO8z5CnVpReBp_8kdrW6clOG12M3T-gbEbwq9b34KVVYQ_DHTIIlbSUTsKF1JwlqKpTy1K7sbMqFTS3V_GiGUTC" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="487" data-original-width="518" height="165" src="https://blogger.googleusercontent.com/img/a/AVvXsEhtjsPNpPvJzA5pNBZttlTX6dhJlcBI5oUb_S4YrYV8_UGH0dZk6RrQ_gQZDQ5QnoHsDOhuXrb6LWXYmJOcZprkpw-qTY5gl2v3ulgAYzO8z5CnVpReBp_8kdrW6clOG12M3T-gbEbwq9b34KVVYQ_DHTIIlbSUTsKF1JwlqKpTy1K7sbMqFTS3V_GiGUTC=w176-h165" width="176" /></a>&nbsp; &nbsp;<a href="https://blogger.googleusercontent.com/img/a/AVvXsEgFC2y7SKXzws1xFRKpRIH2xJvNQg4gs4R0pFgqsbs0aVcUQXzR_BSrouh9P4R6iJ02UH8vaW22lwlKhSScpk7mpeEJKoPD3NCKK1T1TJnJiZv2ZsAz8aDvnJoyG3z-Kdc95bsH6GeFn_hoaakP0NrnKOqdyGijUER7IBWvp01CpqEEMqV5cB87uVrkpZpQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="397" data-original-width="518" height="155" src="https://blogger.googleusercontent.com/img/a/AVvXsEgFC2y7SKXzws1xFRKpRIH2xJvNQg4gs4R0pFgqsbs0aVcUQXzR_BSrouh9P4R6iJ02UH8vaW22lwlKhSScpk7mpeEJKoPD3NCKK1T1TJnJiZv2ZsAz8aDvnJoyG3z-Kdc95bsH6GeFn_hoaakP0NrnKOqdyGijUER7IBWvp01CpqEEMqV5cB87uVrkpZpQ=w201-h155" width="201" /></a></div><br />The built-in debugger now supports the 'step out' command and allows using the mouse to toggle breakpoints and inspect stack traces.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhZtFIOrZxCR1DF8mAXotOySwaYAt6yPMNSs4ZBpI4IRM1WHD2J5YAcLNDGvxrJ7UhVhHLV6fNgOfWhoQ7Tg8Ii09oqu7m9rwlXCYo9xhHYlFz5scByC6Hs_mKN-OHwsRX3BMeOKqQ6WIDpKmIaIGsILaFLRkBysEctXG6_9VEOePGYskmV7SMbRsJ6czjF" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="644" data-original-width="849" height="272" src="https://blogger.googleusercontent.com/img/a/AVvXsEhZtFIOrZxCR1DF8mAXotOySwaYAt6yPMNSs4ZBpI4IRM1WHD2J5YAcLNDGvxrJ7UhVhHLV6fNgOfWhoQ7Tg8Ii09oqu7m9rwlXCYo9xhHYlFz5scByC6Hs_mKN-OHwsRX3BMeOKqQ6WIDpKmIaIGsILaFLRkBysEctXG6_9VEOePGYskmV7SMbRsJ6czjF=w358-h272" width="358" /></a></div><br /><br /><p></p>]]></description>
    </item>
    
    <item>
      <title>DJ’s Sinclair QL Blog is back</title>
      <link>https://dilwyn2.wordpress.com/2026/09/03/djs-sinclair-ql-blog-is-back/</link>
      <guid isPermaLink="true">https://dilwyn2.wordpress.com/2026/09/03/djs-sinclair-ql-blog-is-back/</guid>
      <pubDate>Thu, 03 Sep 2026 21:18:44 -0000</pubDate>
      <description><![CDATA[<p class="wp-block-paragraph">This blog took a break for some time as I felt I needed a bit of a break from the QL scene, a slight burnout happened, and a couple of bereavements happened this year on top of everything else.</p>



<p class="wp-block-paragraph">In theory, retirement meant I should have more time for hobbies like the Sinclair QL. In practice, as many who have recently retired will tell you, it often doesn&#8217;t work like that! People often go &#8220;oh, you&#8217;ve just retired, you&#8217;ll have time to join our committee now!&#8221; or something like that. If anything, I&#8217;ve ended up busier since retiring than I ever was.</p>



<p class="wp-block-paragraph">How often will this blog get updated now? Hard to tell. Much of my QL stuff is back out of storage. Now all I need is the time to do something with it all!</p>



<p class="wp-block-paragraph">One thing I won&#8217;t be doing is restoring my old QL website; that was honestly too much work and I&#8217;m not getting any younger! Since I closed it down, a few kind people on the QL scene took matters into their own hands and kindly made available a copy of the last version of the site, minus a few things like the QL Forum feed, the site search (which is still possible with something like a site-specific Google site: search), but with most if not all of the downloads. Urs König&#8217;s copy of my site is available at <a href="https://sinclairql.net/djw/index.html" target="_blank" rel="noopener">https://sinclairql.net/djw/index.html</a></p>



<p class="wp-block-paragraph">Note that the &#8216;djw&#8217; part of the URL is actually slightly wrong. The well-intentioned Urs mistook old QL company names! Mine was actually DJC, not DJW, which was a different and unrelated company. But never mind, the copy of the site is available for those who remain loyal to the good old Sinclair QL and the retro community in general.</p>



<p class="wp-block-paragraph">If I release any more QL software in the future (and that&#8217;s definitely an IF, not a WHEN), I&#8217;ll probably do so via this blog and anyone will be free to copy anything I release to other more widely accessed QL websites.</p>



<p class="wp-block-paragraph">Here are a few useful links to the QL pages I used most in the past &#8211; I hope they&#8217;re all still there.</p>



<p class="wp-block-paragraph"><a href="https://www.theqlforum.com/">The Sinclair QL Forum &#8211; Index page</a></p>



<p class="wp-block-paragraph"><a href="https://www.facebook.com/groups/30699392074/">Sinclair QL For Everyone | Facebook</a> (QL discussion group on Facebook)</p>



<p class="wp-block-paragraph"><a href="https://home.hccnet.nl/b.spelten/ql/">BSJR QL home</a> (Bob Spelten jr.&#8217;s QL website)</p>



<p class="wp-block-paragraph"><a href="https://www.terdina.net/ql/q-emulator.html" target="_blank" rel="noopener">Q-emuLator Sinclair QL</a> (Daniele Terdina&#8217;s Q-emuLator Sinclair QL emulator)</p>



<p class="wp-block-paragraph"><a href="https://sinclairql.net/djw/index.html" target="_blank" rel="noopener">https://sinclairql.net/djw/index.html</a> (copy of my old QL website)</p>



<p class="wp-block-paragraph"><a href="https://github.com/SinclairQL/" target="_blank" rel="noopener">Sinclair QL Home Computer Development · GitHub</a></p>



<p class="wp-block-paragraph"><a href="https://www.kilgus.net/" target="_blank" rel="noopener">Kilgus.net | Stuff I do</a> (Marcel Kilgus website, includes QPC2 emulator)</p>



<p class="wp-block-paragraph"><a href="https://www.knoware.no/" target="_blank" rel="noopener">Knoware</a> (Per Witte&#8217;s excellent QL page, many downloads)</p>



<p class="wp-block-paragraph"><a href="https://www.rwapsoftware.co.uk/" target="_blank" rel="noopener">Products for Vintage Home Computers</a> (Rich Mellor &#8211; RWAP Software)</p>



<p class="wp-block-paragraph"><a href="https://www.sellmyretro.com/" target="_blank" rel="noopener">Buy &amp; Sell Retro Electronics Home Computers Arcade &amp; Video Games Consoles</a> (SellMyRetro site)</p>



<p class="wp-block-paragraph"><a href="https://www.sinclairql.net" target="_blank" rel="noopener">https://www.sinclairql.net</a> (Urs König QL site)</p>



<p class="wp-block-paragraph"><a href="https://github.com/SinclairQL/sQLux/" target="_blank" rel="noopener">GitHub &#8211; SinclairQL/sQLux · GitHub</a> (sQLux emulator from Graeme Gregory)</p>



<p class="wp-block-paragraph"><a href="http://qdos.free.fr/main.html">Thierry Godefroy&#8217;s Sinclair QL and QDOS compatible systems support site</a></p>



<p class="wp-block-paragraph"><a href="https://www.hunggartorino.it/ql/language/it/">Quantum Technology – Specialisti Sinclair QL</a></p>]]></description>
    </item>
    
    <item>
      <title>El porqué de los modos 512×256 y 256×256</title>
      <link>https://sinclairqles.wordpress.com/2026/07/27/el-porque-de-los-modos-512x256-y-256x256/</link>
      <guid isPermaLink="true">https://sinclairqles.wordpress.com/2026/07/27/el-porque-de-los-modos-512x256-y-256x256/</guid>
      <pubDate>Mon, 27 Jul 2026 19:16:26 -0000</pubDate>
      <description><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-attachment-id="4257" data-permalink="https://sinclairqles.wordpress.com/2026/07/27/el-porque-de-los-modos-512x256-y-256x256/ql_early_concept/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png" data-orig-size="963,722" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="QL_early_concept" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=584" width="545" height="408" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=545" alt="Prototipo QL portátil" class="wp-image-4257" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/ql_early_concept.png 963w" sizes="(max-width: 545px) 100vw, 545px" /></figure>
</div>


<p class="wp-block-paragraph">Imagen: <em>Prototipo inicial del Sinclair QL elaborado por el diseñador Rick Dickinson.</em></p>



<p class="wp-block-paragraph">El lanzamiento del <strong>Sinclair QL</strong> en 1984 introdujo dos modos de vídeo gestionados por su chip a medida (el ZX8301): <strong>512 × 256 píxeles</strong> a 4 colores y <strong>256 × 256 píxeles</strong> a 8 colores. Desde una perspectiva estrictamente matemática, estas configuraciones arrojaban relaciones de aspecto de 2:1 y 1:1 respectivamente. Sin embargo, las pantallas analógicas y los televisores comerciales de la época empleaban un formato físico de <strong>4:3</strong>.</p>



<p class="wp-block-paragraph">Esta desconexión geométrica no obedeció a un criterio estético, sino a un estricto compromiso de ingeniería para equilibrar el coste de los componentes, los límites de la tecnología de televisión y las necesidades del software profesional.</p>



<span id="more-4253"></span>



<p class="wp-block-paragraph">Como diseñador, siempre he odiado la relación de aspecto del video en el QL. El ordenador, en sus inicios, iba a ser un portátil con pantalla plana. Sabía que Sinclair trabajó duro para crear una pantalla con esta característica, y suponía que otras marcas habrían podido servirle un producto equivalente para el lanzamiento de su nueva máquina. En ambos casos <strong>asumí que la resolución de video del QL estaba condicionada por las especificaciones de los fabricantes de las pantallas</strong>, pero me puse a indagar con la IA, y descubrí que las causas de su desproporción pudieron ser otras.</p>



<h2 class="wp-block-heading">La pantalla plana de Sinclair</h2>



<p class="wp-block-paragraph">La portabilidad del Sinclair QL quedó truncada al cancelarse el desarrollo de su monitor integrado. Inicialmente, el proyecto —bajo el nombre en clave ZX83— iba a incorporar una pantalla plana de 4 pulgadas basada en el tubo CRT plano que Sinclair había desarrollado para su televisión de bolsillo TV80, evolución del prototipo FTV2. A diferencia de los LCD de la época, de contraste deficiente, este tubo desviaba el cañón de electrones de forma lateral para reducir drásticamente la profundidad del aparato. Sin embargo, la viabilidad técnica se interpuso en el camino: la compleja electrónica necesaria para corregir la distorsión de la imagen, junto con el elevado coste de fabricación y su modesta resolución, hacían inviable su producción en masa para un ordenador de negocios. Ante esta falta de rentabilidad comercial, Sir Clive Sinclair ordenó cancelar la TV plana a finales de 1983, obligando a rediseñar el QL sobre la marcha como un equipo de sobremesa convencional con salidas de vídeo estándar.</p>



<h2 class="wp-block-heading">1. El límite económico de los 32 KB de RAM</h2>



<p class="wp-block-paragraph">En la primera mitad de los años 80, la memoria RAM representaba uno de los costes de fabricación más elevados. Para comercializar el QL a un precio competitivo (399 libras en su lanzamiento), los diseñadores de Sinclair Research limitaron el presupuesto de la memoria asignada a vídeo a <strong>32 KB fijos</strong> de un total de 128 KB que traía la máquina. Sólo la pantalla ocupaba una cuarta parte de la RAM disponible, aunque, por suerte, el QL permitía expansión de RAM.</p>



<p class="wp-block-paragraph">Sinclair fijó este tamaño de la pantalla como un compromiso entre coste de memoria y ancho de banda del bus. La resolución de pantalla, además de ser económicamente ajustada, encajaba de forma exacta en la memoria principal sin requerir circuitería adicional de decodificación:</p>



<p class="has-text-align-center wp-block-paragraph"><strong>512 x 256 x 2 bits / 8 bits por byte = 32.768 bytes </strong>(exactamente 32 KB)</p>



<p class="wp-block-paragraph">El modo de 256 x 256 comparte exactamente los mismos 32 KB de memoria, ya que el ZX8301 interpreta los mismos bytes de dos formas distintas según el modo que esté en uso.</p>



<p class="wp-block-paragraph">Lograr una resolución más cercana a la proporción 4:3 con el mismo ancho de 512 píxeles (por ejemplo, 512 × 384), requeriría 48 KB de RAM. Esto hubiera reducido la RAM disponible para el resto del sistema, entre otras cosas, como veremos.</p>



<h2 class="wp-block-heading"><strong>2. El cuello de botella del bus compartido</strong></h2>



<p class="wp-block-paragraph">El QL contaba con un 68008, una versión de 8 bits del 68000, con registros de 32 bits pero bus de datos de 8 bits, y con el chip de video  a medida ZX8301. Además de una salida de video compuesto/UHF para televisores domésticos, el QL incorporaba una salida RGB TTL directa para monitores dedicados.</p>



<p class="wp-block-paragraph">Para ahorrar piezas, se usó un único bloque de memoria compartida para todo el mapa de memoria del sistema, obligando al procesador y al chip de vídeo a utilizar los mismos cables internos para acceder a ella. Si bien esto era común en los ordenadores de la época, otras máquinas usaban trucos de hardware o chips con funciones gráficas dedicadas. Como el chip de vídeo necesitaba leer constantemente esa memoria compartida para evitar que la imagen del televisor se borrase (refresco de pantalla), acaparaba el bus de memoria, imponiendo tiempos de espera al procesador cada vez que este necesitaba leer o escribir en la RAM compartida, reduciendo su ancho de banda efectivo a algo menos de la mitad, y parte de ese tiempo disponible de la CPU se dedicaba a hacer todo el trabajo con la memoria de pantalla (pintado de caracteres, instrucciones gráficas&#8230;).</p>



<p class="wp-block-paragraph">Para intentar aliviar este atasco, Sinclair utilizó una técnica pionera en ordenadores domésticos: hacer que el chip de vídeo leyera la memoria en ráfagas rápidas de varios datos seguidos, ganando un tiempo muy valioso. Sin embargo, este parche tenía un límite marcado por la imagen de la televisión, que se redibujaba 50 veces por segundo (50 Hz), dando al chip de vídeo una ventana de solo 20 milisegundos para procesar cada imagen:</p>



<p class="has-text-align-center wp-block-paragraph"><strong>1.000 milisegundos / 50 veces por segundo = 20 milisegundos</strong></p>



<p class="wp-block-paragraph">Aumentar la resolución del modo de pantalla o añadir más colores por píxel habría obligado al chip de vídeo a pedir más información dentro de ese mismo lapso de tiempo, tardando más, saturando el bus de nuevo e imponiendo aún más tiempos de espera al procesador.</p>



<p class="wp-block-paragraph">Esta disputa constante por los buses fue el motivo clave para limitar la pantalla a 32 KB. El ZX8301 no lee los datos de golpe, sino que accede a la RAM mediante pequeñas ráfagas continuas a lo largo de los 20 milisegundos de cada fotograma (acaparando 8 de cada 12 ciclos de reloj). Un mapa de pantalla superior a 32 KB habría exigido un ancho de banda mayor, dejando a la CPU sin los pocos ciclos de acceso que le quedaban para trabajar antes del siguiente refresco de imagen que volvería a frenarlo.</p>



<h3 class="wp-block-heading">Daños colaterales</h3>



<p class="wp-block-paragraph">La búsqueda obsesiva de la velocidad en las ráfagas no solo fijó el tamaño de la pantalla, sino que condicionó la propia codificación de sus píxeles en memoria. En el modo de 4 colores (negro, rojo, verde y blanco), el ZX8301 lee dos planos de bits alternos (R y G) mediante su acceso en ráfaga de 4 bytes, lo que le permite construir 8 píxeles consecutivos con solo dos accesos a memoria. En el modo de 8 colores, empaqueta 4 bits por píxel en palabras de 16 bits, duplicando la profundidad de color dentro del mismo ancho de banda. Un planteamiento brillante para la época pero con importantes consecuencias en el rendimiento gráfico.</p>



<p class="wp-block-paragraph">Respecto a esa codificación de color, como curiosidad y ejemplo de las limitaciones del hardware, el QL mostraba 8 colores usando 3 bits de color (R, G, B) + 1 bit de parpadeo (FLASH) en vez de 16 colores en el modo de 256 x 256. El ZX8301 generaba señales de vídeo en formato <strong>RGB TTL</strong> (0 V para nivel bajo o apagado, 5 V para nivel alto o encendido) por cada canal de color. Debido a esta naturaleza binaria, solo podía manejar encendido/apagado (1 bit) por canal. Los monitores dedicados de la época (como el Vision QL) recibían directamente esta señal RGB digital, por lo que <strong>estaban limitados a 8 colores base</strong> (negro, azul, rojo, magenta, verde cian amarillo y blanco). A cambio, la nitidez y definición de la imagen eran muy superiores a las de un televisor doméstico. El <strong>control de brillo</strong> en el monitor cambiaba la intensidad luminosa de los colores mostrados, pero no el color.</p>



<p class="wp-block-paragraph">El parámetro <strong>FLASH</strong> es un bit que conmuta el estado de cada píxel (haciéndolo parpadear a intervalos regulares de tiempo). Sinclair prefirió el parpadeo por píxel porque el QL se vendía como máquina de negocios, y en interfaces de texto (procesadores de texto, hojas de cálculo) el parpadeo es más útil que un color extra para resaltar campos, cursores o errores.</p>



<h2 class="wp-block-heading">3. La barrera de los 50 Hz (PAL) y el entrelazado</h2>



<p class="wp-block-paragraph">El QL fue diseñado para poder utilizarse con los televisores domésticos convencionales mediante el estándar de vídeo analógico PAL. El estándar PAL emite a 50 Hz y consta de 625 líneas en total, pero divididas en dos campos entrelazados (líneas pares e impares) de 312,5 líneas cada uno.</p>



<p class="wp-block-paragraph">Al descontar las líneas ocultas utilizadas para la sincronización vertical de la pantalla, los ordenadores domésticos solo disponían de una ventana útil de entre 256 y 288 líneas para emitir vídeo en modo progresivo (no entrelazado). Fijar la <strong>resolución vertical en 256 líneas</strong> garantizaba una señal estable y compatible con cualquier televisor.</p>



<p class="wp-block-paragraph">Superar las 288 líneas verticales para aproximarse a la geometría 4:3 habría obligado al hardware a emitir en modo entrelazado. En los monitores CRT comunes de la época, el texto de alta densidad parpadeaba de forma severa al entrelazarse, provocando fatiga visual inmediata al usuario.</p>



<h3 class="wp-block-heading"><strong>Overscan horizontal</strong></h3>



<p class="wp-block-paragraph">Otra curiosidad. Debido a que las constantes de tiempo del ZX8301 se diseñaron pensando en el tubo CRT plano del prototipo original y no en un televisor convencional, el modo de alta resolución (Modo 4) sufre un claro <strong><em>overscan</em> horizontal</strong> (la imagen se sale por los lados). Esto ocurre porque sus 512 píxeles ocupan 51,2 microsegundos de la línea de vídeo, superando la ventana segura de 48 microsegundos que garantiza el estándar PAL. El resultado era que cualquier televisor común recortaba los bordes, perdiendo entre 24 y 32 píxeles de la imagen repartidos en los laterales —algo que solo se veía completo en monitores especiales como el <em>Vision QL</em>—. Un problema que no se solucionó en la fase de diseño.</p>



<h2 class="wp-block-heading">4. El requisito de las 80 columnas empresariales</h2>



<p class="wp-block-paragraph">El Sinclair QL se posicionó como un ordenador orientado al mercado ejecutivo y de oficina, incluyendo una suite ofimática desarrollada por PSION. Para trabajar con garantías en tareas de procesamiento de textos u hojas de cálculo, el estándar de la industria exigía visualizar un mínimo de <strong>80 columnas de texto</strong>.</p>



<p class="wp-block-paragraph">Al optar por un <strong>ancho de 512 píxeles</strong>, el QL podía renderizar fuentes tipográficas con una matriz de 6 píxeles de ancho por carácter, logrando acomodar <strong>85 columnas de texto</strong> legibles en la pantalla.</p>



<h2 class="wp-block-heading">Soluciones alternativas en el mercado de 1984</h2>



<p class="wp-block-paragraph">Frente a este mismo dilema técnico, otros fabricantes optaron por caminos distintos según su público objetivo y rango de precio:</p>



<ul class="wp-block-list">
<li><strong>Apple Macintosh (El camino del monitor dedicado):</strong> Lanzado el mismo año que el QL, Apple decidió prescindir por completo de la compatibilidad con televisores. El Macintosh original adoptó una resolución de <strong>512 × 342 píxeles</strong>. Para evitar el parpadeo en las 342 líneas, Apple integró un monitor interno de alta frecuencia (22 kHz progresivo en lugar de los 15 kHz de un televisor). Esto permitía píxeles casi cuadrados muy nítidos para oficina, pero disparó el precio de la máquina a 2.495 dólares. Además, al funcionar estrictamente en blanco y negro (1 bit por píxel), el consumo de RAM se contuvo en unos eficientes 21,8 KB.</li>



<li><strong>IBM PC con tarjeta CGA (El camino del texto estirado):</strong> Para resolver las 80 columnas bajo el límite de los televisores, el estándar CGA de IBM recurrió al modo de <strong>640 × 200 píxeles</strong> en blanco y negro (16 KB de RAM). Esto generaba una proporción matemática de 3.2:1, lo que en una pantalla física de 4:3 producía píxeles notablemente más planos y estirados que los del Sinclair QL, deformando de forma severa cualquier elemento gráfico circular o cuadrado.</li>
</ul>



<h2 class="wp-block-heading">Píxeles anamórficos</h2>



<p class="wp-block-paragraph">Como consecuencia del diseño, el Sinclair QL operaba con <strong>píxeles rectangulares (anamórficos)</strong>, altos y delgados. Era el propio tubo de rayos catódicos de la pantalla el que se encargaba de estirar físicamente el haz de electrones analógico para cubrir la superficie de cristal 4:3. Los desarrolladores de software debían introducir <a href="https://github.com/JavGuerra/Sinclair-QL-SuperBASIC-goodies/blob/main/docs/x_scale.md">factores de corrección geométrica</a> en sus rutinas de dibujo para evitar que un círculo perfecto en memoria se mostrase en pantalla con forma elíptica.</p>



<figure class="wp-block-image size-full"><img data-attachment-id="4259" data-permalink="https://sinclairqles.wordpress.com/2026/07/27/el-porque-de-los-modos-512x256-y-256x256/jav_bg_drop/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg" data-orig-size="512,256" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;Picasa 2.6&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="jav_bg_drop" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg?w=512" width="512" height="256" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg" alt="Imagen 512x256" class="wp-image-4259" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg 512w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg?w=150&amp;h=75 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/jav_bg_drop.jpg?w=300&amp;h=150 300w" sizes="(max-width: 512px) 100vw, 512px" /><figcaption class="wp-element-caption">Imagen a 4 colores con resolución 512&#215;256 usando píxeles cuadrados antes de ser deformados.</figcaption></figure>



<p class="wp-block-paragraph">En el modo de 256 x 256, el píxel rectangular se estiraba horizontalmente. La pantalla tenía menos pixels de ancho, exactamente la mitad, pero a cambio ofrecía 8 colores en vez de 4, y cada pixel ocupaba 4 bits en vez de 2 con la misma cantidad de RAM. Como vimos, uno de esos bits se usaba para activar o desactivar el FLASH o parpadeo de píxel, una característica inusual que operaba directamente en hardware sin consumir ciclos de CPU.</p>



<h2 class="wp-block-heading">El legado de un diseño condicionado</h2>



<p class="wp-block-paragraph">El Sinclair QL permanece como un claro ejemplo de la informática de transición de mediados de los ochenta, donde cada decisión de diseño gráfico estaba sujeta a una estricta realidad física y económica. La elección de las resoluciones de 512 × 256 y 256 × 256 píxeles demuestra que el verdadero reto de Sinclair Research no fue la falta de visión tecnológica, sino la necesidad de equilibrar los costes de producción con los límites técnicos de los hogares de la época. Aunque el uso de píxeles anamórficos obligó a los programadores a realizar encajes matemáticos para evitar deformaciones en las pantallas 4:3, esta arquitectura permitió ofrecer una máquina con capacidades de visualización empresariales a una fracción del coste de sus competidores directos.</p>



<p class="wp-block-paragraph"><strong>Saber más:</strong></p>



<ul class="wp-block-list">
<li><a href="https://www.flickr.com/photos/9574086@N02/albums/72157600854938578/with/16205461566">QL and Beyond</a>, galería de prototipos del QL por Rick Dickinson, su diseñador.</li>



<li><a href="https://youtu.be/w3SbEZazxVY?si=S9EHADIUE04a4PlU">TV80 FTV1</a>, Video de desmontaje y funcionamiento.</li>



<li><a href="https://github.com/JavGuerra/Sinclair-QL-SuperBASIC-goodies/blob/main/docs/x_scale.md">X_Scale</a>, función SuperBASIC para corrección de geometría.</li>



<li><a href="https://sinclairqles.wordpress.com/2014/03/25/sistema-de-coordenadas-en-el-ql/">Sistema de coordenadas en el QL, la sentencia SCALE</a>.</li>



<li><a href="https://foro.speccy.org/viewtopic.php?p=15624&amp;hilit=acid+face#p15624">Acid Face</a>, un ejemplo de uso de la función para corrección de geometría.</li>



<li><a href="https://sinclairqles.wordpress.com/2013/04/13/volcar-imagenes-en-la-pantalla-del-ql/">Volcar imagenes en la pantalla del QL</a>.</li>



<li><a href="https://sinclairqles.wordpress.com/2009/07/01/gmove-by-linus-torvalds/">GMOVE</a>, comando para mover bloques de pixels en pantalla.</li>



<li><a href="https://badaman.badared.com/ql/artiql/qlpal.htm">Exprimiendo la paleta del QL</a>, con <a href="https://badaman.badared.com/ql/artiql/qlpal/galeria.htm">ejemplos</a>.</li>



<li><a href="https://badaman.badared.com/ql/images/index.htm">Galería de imágenes en modo 4</a>, 512 x 256 a 4 colores.</li>
</ul>]]></description>
    </item>
    
    <item>
      <title>MyLISP para Sinclair QL: cómo acabé escribiendo mi propio LISP (y por qué tiene la culpa una calculadora)</title>
      <link>https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/</link>
      <guid isPermaLink="true">https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/</guid>
      <pubDate>Sat, 25 Jul 2026 21:44:11 -0000</pubDate>
      <description><![CDATA[<p class="wp-block-paragraph">por Daniel Fernandez Santos (@dfernandez132)</p>



<p class="wp-block-paragraph">Voy a confesar una cosa antes de empezar: de adolescente yo miraba mi calculadora con la misma cara con la que otros miraban el póster de un coche deportivo. Mi HP48 no tenía ruedas ni aceleraba de 0 a 100, pero hacía algo que a mí me parecía mucho más increíble: le escribías <strong>3 X SQ</strong> (o cosas parecidas, no me hagáis examinarme de RPL treinta años después) y te devolvía <strong>2·X</strong>. No un número. Una expresión. Con una letra dentro. La calculadora sabía derivar sin saber cuánto valía X, y eso, para un chaval con más curiosidad que paciencia, era sencillamente magia negra.</p>



<p class="wp-block-paragraph">La HP48 —y toda la familia de calculadoras RPL de Hewlett-Packard que la precedieron— tenían una forma de trabajar rarísima si venías de una Casio normal: notación polaca inversa, pila en lugar de línea de edición, y un lenguaje interno, RPL («Reverse Polish Lisp», nada menos), que era en el fondo un LISP disfrazado de calculadora científica. La primera de la saga en atreverse con álgebra simbólica fue la HP-28C, allá por 1987: la primera calculadora de bolsillo capaz de resolver ecuaciones y derivar sin convertirlo todo en decimales. La HP48, que llegó en 1990, heredó ese motor y lo hizo legendario entre estudiantes de ingeniería y gente rara como yo, que la miraba de reojo mientras tecleaba BASIC en mi ZX Spectrum, preguntándome cómo demonios hacía aquello.</p>



<figure class="wp-block-image size-large"><img width="589" height="433" data-attachment-id="4232" data-permalink="https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/mylisp3/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg" data-orig-size="589,433" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="mylisp3" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg?w=589" alt="" class="wp-image-4232" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg 589w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp3.jpeg?w=300 300w" sizes="(max-width: 589px) 100vw, 589px" /><figcaption class="wp-element-caption"><em>Calculadora HP 48SX. Por ella empezó todo</em></figcaption></figure>



<p class="wp-block-paragraph">Han pasado bastantes años —los suficientes como para que ya no me sorprenda tanto ver una máquina hacer cuentas, pero sí los bastantes como para que la pregunta se quedara ahí, aparcada, sin resolver—. Y un día, ya mayor y con menos inocencia, pero exactamente las mismas ganas de curiosear, decidí volver a esa pregunta: ¿cómo lo hacía?</p>



<p class="wp-block-paragraph">La respuesta, como suele pasar, era menos mágica y más bonita de lo que esperaba: dentro de aquella calculadora vivía un <strong>CAS</strong>, un Computer Algebra System, un sistema de álgebra computacional. Un programa capaz de manipular expresiones matemáticas como símbolos, no como números: sabe que d/dx (x²) = 2x sin necesidad de que le digas cuánto vale x, igual que sabe simplificar, factorizar o sustituir una variable por otra expresión. No calcula el resultado: calcula <em>la fórmula del resultado</em>.</p>



<p class="wp-block-paragraph">Los CAS no nacieron con la HP48, ni de lejos. La abuela de todos es Macsyma, un proyecto financiado por DARPA (Defense Advanced Research Projects Agency) que arrancó en el MIT <strong>(Massachusetts Institute of Technology</strong>) allá por 1968 y que durante años sólo pudo correr en mainframes del tamaño de una habitación. A finales de los 70, en Honolulu, dos programadores (Albert Rich y David Stoutemyer, con su empresa Soft Warehouse) consiguieron meter algo parecido en un ordenador de mesa: se llamó muMATH, y de ahí, en 1988, nacería Derive, uno de los primeros CAS que la gente de a pie pudo tener en su propio PC. Ese linaje —Macsyma, muMATH, Derive, Reduce— es el que, con calculadoras de por medio, acabó llegando a mi HP48 sin que yo lo supiera. Hoy en día todo programa serio de cálculo simbólico —Mathematica, Maple, Maxima (descendiente directo de Macsyma y aún vivo), el Symbolic Toolbox de MATLAB, Octave, SageMath— es, en el fondo, un pariente lejano de aquella misma idea.</p>



<p class="wp-block-paragraph">Bien, misterio resuelto&#8230; o casi. Porque en cuanto entiendes qué es un CAS te asalta la siguiente pregunta, mucho más peligrosa: ¿y cómo se programa uno? Ahí descubres que hay dos caminos. Puedes escribirlo en Pascal, en C, en el lenguaje que quieras, tratando las expresiones como si fueran texto o estructuras de datos genéricas, a base de fuerza bruta y mucho if. O puedes hacerlo en el lenguaje que fue literalmente diseñado para esto: LISP (recuerda lo que significaba la última letra del lenguaje de HP <strong>RPL</strong>), donde una expresión matemática y una expresión de código son, estructuralmente, la misma cosa: una lista. En LISP, escribir un CAS no es forzar la herramienta contra su naturaleza; es usarla para aquello que la inventaron.</p>



<p class="wp-block-paragraph">Y ahí fue cuando me dije: «así aprendo LISP». Porque LISP tiene una historia tan larga como entretenida. Lo inventó John McCarthy en 1958, en el MIT, y ni siquiera pretendía que se ejecutara en un ordenador de verdad: era una notación matemática para razonar sobre funciones. Alguien lo implementó a mano sobre un IBM 704 (uno de los estudiantes de John), y ahí quedó fijado para siempre uno de los detalles más entrañablemente absurdos de la informática: las funciones CAR y CDR, que hoy en día significan simplemente «primer elemento» y «resto de la lista», vienen literalmente de los nombres de dos registros de aquella máquina: Contents of Address Register y Contents of Decrement Register. Sesenta y pico años después seguimos usando el nombre de un registro de hardware que ya no existe, y a nadie le extraña. Eso es LISP: un lenguaje que arrastra su propia arqueología con orgullo. En los 80 hasta hubo ordenadores enteros diseñados solo para ejecutar LISP —las célebres «Lisp machines» de Symbolics y compañía— antes de que el enfriamiento de la inteligencia artificial se las llevara por delante. Es, después de Fortran, el lenguaje de programación de alto nivel más antiguo que sigue vivo.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="590" height="442" data-attachment-id="4235" data-permalink="https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/mylisp2-2/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg" data-orig-size="590,442" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="mylisp2" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg?w=590" alt="" class="wp-image-4235" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg 590w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp2-1.jpeg?w=300 300w" sizes="auto, (max-width: 590px) 100vw, 590px" /><figcaption class="wp-element-caption"><em>IBM serie 700</em></figcaption></figure>



<p class="wp-block-paragraph">Y ya puestos a aprender LISP, quise hacerlo en el ordenador de la época de mi HP48 que entonces no tenía, pero miraba con curiosidad, comparándolo con mi ZX Spectrum: el Sinclair QL. El mismo que hoy tengo de verdad y con el que disfruto muchísimo.</p>



<p class="wp-block-paragraph">Así que me puse manos a la obra: necesitaba un intérprete de LISP para construir mi CAS encima. Miré lo que había disponible&#8230; y, ya puestos, hice lo que haría cualquier español de a pie, pronunciar la fatídica frase: “Sujétame el cubata, que esto lo hago yo”.</p>



<p class="wp-block-paragraph">El resultado se llama <strong>MyLISP</strong>.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="612" height="412" data-attachment-id="4238" data-permalink="https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/mylisp1-2/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg" data-orig-size="612,412" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="mylisp1" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg?w=612" alt="" class="wp-image-4238" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg 612w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp1-1.jpeg?w=300 300w" sizes="auto, (max-width: 612px) 100vw, 612px" /></figure>



<p class="wp-block-paragraph">MyLISP es un intérprete LISP-1 (sin entrar en tecnicismos: en un LISP-1 las funciones y las variables comparten el mismo «cajón» de nombres —si defines una variable LIST, adiós función LIST—, mientras que en un LISP-2, como Common Lisp, cada cosa tiene su cajón separado. Es más, una cuestión de filosofía que de complejidad, pero define bastante el carácter del lenguaje). Y desde el primer boceto tuve clara una cosa: no quería un LISP de juguete que sólo sirviera para sumar (1 2 3) en una demo. Quería algo usable de verdad, con closures, listas, recursión seria y una gestión de memoria decente — y eso, en un ordenador de aquella época, exige mínimo 640 KB de RAM para que mi niño respire con comodidad.</p>



<p class="wp-block-paragraph">Antes de ponerme a teclear, tocaba elegir arma. Lo primero que descarté fue SuperBASIC: es estupendo para casi todo en un QL, pero un intérprete LISP necesita construir sus propias estructuras de datos dinámicas (listas enlazadas, celdas, punteros) con un control fino sobre la memoria, y eso es justo lo que SuperBASIC no te da. Así que volví, con nostalgia, al Pascal de mi juventud: quería recuperar aquella programación imperativa de toda la vida, la de los record y punteros, y el QL me ponía sobre la mesa nada menos que tres compiladores distintos para elegir.</p>



<p class="wp-block-paragraph">El primero, <strong>Computer One Pascal</strong>, cayó rápido: compilaba a P-code (una máquina virtual dentro de la máquina), lentísimo para un evaluador recursivo tipo Eval/Apply, y encima tenía un bug de traca que colgaba el sistema en cuanto el QL tenía 1 MB de RAM o más —justo la memoria que yo necesitaba aprovechar. El segundo, <strong>Metacomco Pascal</strong>, tenía detrás una empresa de las buenas (los mismos que hicieron AmigaDOS y el BCPL para el QL), pero su compilador de Pascal era, según todas las notas históricas de la época, un desastre de bugs y rendimiento; el consejo unánime de la comunidad se resumía en dos palabras: «Forget it!». Así que me quedé con el tercero, <strong>Prospero Pascal</strong>, el que todo el mundo coincide en señalar como el estándar de facto del QL: certificado ISO 7185, código máquina nativo del 68000 (nada de P-code), coma flotante IEEE de precisión simple y doble hecha a mano, y un enlazador capaz de compilar módulos grandes por separado, algo imprescindible en cuanto tu intérprete Lisp empieza a crecer por encima de las dos mil líneas. Tiene un inconveniente casi entrañable —exige un cartucho EPROM pinchado en el puerto de expansión para funcionar, un dongle antipiratería de otra época—, pero a cambio es el único de los tres capaz de sostener sin temblar un motor de recolección de basura mark &amp; sweep y aritmética exacta. Vamos, que, si iba a jugar en serio, tenía que ser con Prospero.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="563" height="800" data-attachment-id="4250" data-permalink="https://sinclairqles.wordpress.com/2026/07/25/mylisp-para-sinclair-ql-como-acabe-escribiendo-mi-propio-lisp-y-por-que-tiene-la-culpa-una-calculadora/mylisp4/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg" data-orig-size="563,800" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="mylisp4" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg?w=563" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg?w=563" alt="" class="wp-image-4250" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg 563w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg?w=106 106w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/07/mylisp4.jpeg?w=211 211w" sizes="auto, (max-width: 563px) 100vw, 563px" /><figcaption class="wp-element-caption"><em>Anuncio de Prospero Software en QL World Marzo 1986</em></figcaption></figure>



<p class="wp-block-paragraph">Tras mil peripecias, unos cuantos bugs de los que sacan canas y algún que otro momento de «por qué me habré metido en esto», conseguí un intérprete que creo que está a la altura de enseñarse a la comunidad sin sonrojo.</p>



<p class="wp-block-paragraph">Con MyLISP funcionando, tocaba la segunda mitad del plan: el CAS. Ahí tuve que aprender de verdad qué son las funciones lambda, qué son las funciones de orden superior, cómo se manipulan listas cuando las listas son el programa, y qué es eso del «azúcar sintáctico» que tanto mencionan los libros de LISP. Y aquí toca un agradecimiento sincero: Claude ha sido un maestro paciente en todo este tramo, explicándome con calma lo que necesitaba entender de los sistemas de álgebra simbólica hasta que las piezas encajaron.</p>



<p class="wp-block-paragraph">El resultado es un CAS completo, escrito enteramente en MyLISP, corriendo en mi QL. Y funciona de verdad. Si le doy la expresión 3x² de forma que la entienda:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">(DEFINE E (MAKEPROD 3 (MAKEPOW &apos;X 2)))</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Y le digo que me la derive respecto a x:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">(DERIVA E &apos;X)              </div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Me devuelve:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">&gt; (* 6 X)</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Pero si además le digo que quiero que me lo ponga en notación matemática normal:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">(PRINTMAT (DERIVA E &apos;X))  </div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Me muestra en pantalla:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">&gt; 6x </div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Ese 6x es el CAS imprimiendo la derivada en, después de haberla calculado símbolo a símbolo.</p>



<p class="wp-block-paragraph">Y aun podemos hacer más cosas con este CAS para LISP (observad que he dicho para LISP no para QL…. eso en el próximo articulo) no se queda en derivadas de una variable: también hace derivadas parciales cruzadas, sustituciones, simplificación de árboles de expresiones, orden canónico de términos semejantes&#8230; el motor completo que treinta años atrás yo miraba con ojos como platos en la pantallita de mi HP48, ahora vive, hecho a mano, en el ordenador que de niño ni siquiera tenía.</p>



<p class="wp-block-paragraph">Se cierra el círculo. Con más canas, pero con las mismas ganas.</p>



<p class="wp-block-paragraph">Podéis acceder a MyLisp en <a href="https://github.com/dfernande132/MyLISP">https://github.com/dfernande132/MyLISP</a></p>



<p class="wp-block-paragraph">Podéis acceder al CAS en <a href="https://github.com/dfernande132/MyLISP-CAS">https://github.com/dfernande132/MyLISP-CAS</a></p>



<p class="wp-block-paragraph">Prospero Pascal está disponible en la página de Dilwyn: <a href="https://sinclairql.net/djw/language/index.html">https://sinclairql.net/djw/language/index.html</a></p>



<p class="wp-block-paragraph">Daniel Fernandez Santos , Julio 2026</p>]]></description>
    </item>
    
    <item>
      <title>Crónicas RU Sinclair QL y Compatibles 2026</title>
      <link>https://sinclairqles.wordpress.com/2026/05/09/cronicas-ru-sinclair-ql-y-compatibles-2026/</link>
      <guid isPermaLink="true">https://sinclairqles.wordpress.com/2026/05/09/cronicas-ru-sinclair-ql-y-compatibles-2026/</guid>
      <pubDate>Sat, 09 May 2026 18:42:36 -0000</pubDate>
      <description><![CDATA[<p class="wp-block-paragraph">Por Miguel Ángel Rojo</p>



<p class="wp-block-paragraph">El día 1 de mayo, en Majadas de Tiétar comenzó con la llegada de los asistentes al evento RU Sinclair QL y Compatibles 2026 el viernes: Badaman, Zerover, Naspternds, TitoxUnix, DFSantos, Salvador Merino, Álvaro Alea y un servidor, Miguel Ángel Rojo. Desempaquetamos todas las máquinas que habíamos traído, dejándolas preparadas para la mañana siguiente, cuando comenzaría el esperado cacharreo. Así pudimos empezar a ver todo lo que los compañeros habían llevado al encuentro. La experiencia fue increíble. Mesa por mesa, tanto el hardware clásico como los desarrollos modernos, exponían la historia completa del Sinclair QL, desde sus inicios hasta la actualidad, demostrando que sigue más vivo que nunca.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="408" data-attachment-id="4206" data-permalink="https://sinclairqles.wordpress.com/encuentro_01/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg" data-orig-size="2048,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Encuentro_01" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=545" alt="" class="wp-image-4206" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/encuentro_01.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<span id="more-4200"></span>



<p class="wp-block-paragraph">Lo primero que llamó nuestra atención fue la impresionante mesa que Naspternds había preparado, presidida por varios Sinclair QL acompañados de sus monitores. Entre todas las piezas destacaban una codiciada Aurora y un espectacular CST Thor, adquirido recientemente y equipado con una unidad Gotek.</p>



<p class="wp-block-paragraph">Durante la jornada se realizó el esperado unboxing de una nueva tarjeta QLion Gold recién llegada de Grecia. El dispositivo demostró ser una maravilla, nos permitimos disfrutar de títulos como Old Tower, acompañado de una excelente calidad de sonido gracias al QLSound, así como del magnífico Batman remake de Joan Gayón.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="409" data-attachment-id="4205" data-permalink="https://sinclairqles.wordpress.com/cst_thor_07/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg" data-orig-size="1600,1201" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="CST_Thor_07" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=545" alt="" class="wp-image-4205" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/cst_thor_07.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Otro de los grandes atractivos fue la presencia de una interfaz QL MIDI de Miracle Systems, una pieza extremadamente rara de la que apenas existen unidades en circulación. Gracias a este hardware pudimos reproducir archivos MIDI a través de un equipo japonés especializado, ofreciendo una experiencia sonora excepcional y convirtiéndose en uno de los momentos más memorables del encuentro.</p>



<figure class="wp-block-image size-full"><img data-attachment-id="4209" data-permalink="https://sinclairqles.wordpress.com/ql_midi_01/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/ql_midi_01.jpg" data-orig-size="450,600" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="QL_MIDI_01" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/ql_midi_01.jpg?w=450" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/ql_midi_01.jpg" alt="" class="wp-image-4209" /></figure>



<p class="wp-block-paragraph">La activa implicación con la plataforma por la clonación y desarrollo de hardware para QL de TitoUnix, le convierten en uno de los miembros más participativos de la escena. Entre sus proyectos más destacados se encuentra la QubIDE clónica, además de diversas ampliaciones de memoria para el sistema, el ensamblaje de la nueva placa base de QL completamente funcional, especialmente teniendo en cuenta la dificultad de localizar determinados componentes originales, aunque todavía es posible encontrar algunos de ellos.</p>



<p class="wp-block-paragraph">TitoxUnix continúa desarrollando nuevas ideas y proyectos para la plataforma, demostrando que la escena del Sinclair QL sigue muy viva gracias al entusiasmo y dedicación de desarrolladores y aficionados como él, también pudimos ver su impresionante colección de Qls.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="410" data-attachment-id="4208" data-permalink="https://sinclairqles.wordpress.com/placas/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg" data-orig-size="2040,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="placas" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=545" alt="" class="wp-image-4208" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/placas.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Zerover también nos presentó su trabajo en el desarrollo de un teclado para Sinclair QL, además de una gran cantidad de PCB diseñadas por él mismo mediante KiCad.</p>



<p class="wp-block-paragraph">Su mesa despertó un enorme interés entre los asistentes, no solo por la calidad de sus diseños, sino también por la dedicación y el esfuerzo invertidos en cada uno de los proyectos mostrados.</p>



<p class="wp-block-paragraph">Otro de los momentos destacados fue el intento de recuperar un microdrive de PSION adquiridos recientemente por un nuevo aficionado al mundo del QL. Se trata de una situación con la que muchos de los que hemos llegado más tarde a esta plataforma nos hemos encontrado: microdrives averiados, cintas deterioradas o membranas quebradas forman parte de la experiencia habitual de restauración y conservación de estos equipos históricos. Durante las pruebas se intentó realizar una copia de seguridad del software, sin embargo, al tratarse de programas de PSION protegidos y en versión española, todo apunta a que únicamente funcionarían correctamente en un QL español, presentando incompatibilidades con sistemas equipados con la ROM inglesa.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="408" data-attachment-id="4213" data-permalink="https://sinclairqles.wordpress.com/teclados/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg" data-orig-size="2048,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="teclados" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=545" alt="" class="wp-image-4213" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/teclados.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Álvaro Alea también nos deleitó con una impresionante colección de placas y desarrollos fabricados por él mismo, demostrando un enorme conocimiento técnico y una gran dedicación al ecosistema del Sinclair QL.</p>



<p class="wp-block-paragraph">Entre sus proyectos, destacaban varias expansiones RAM -que también están publicadas en su GitHub-, diseñadas para ampliar la memoria del sistema y ofrecer nuevas posibilidades a los usuarios del QL. También pudimos ver clones de la QL Trump Card, una de las expansiones más reconocidas dentro de la plataforma por sus capacidades de memoria y gestión de discos. Entre sus diseños en GitHub se pueden ver cantidad de proyectos: Interfaz de SINCLAIR QL Mini Trump Card, QL 4 floppy addon, Minerva MK2, un clon de la conocida placa ROM pensado para aportar mayor funcionalidad y estabilidad al equipo…</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="408" data-attachment-id="4220" data-permalink="https://sinclairqles.wordpress.com/titox_alvaro_dani/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg" data-orig-size="2048,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Titox_Alvaro_Dani" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=545" alt="" class="wp-image-4220" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_alvaro_dani.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Otro de los desarrollos que despierta gran interés en el mundillo QL es la tarjeta de sonido QLsound, capaz de mejorar notablemente las capacidades de audio del sistema original. Todos estos proyectos reflejan el excelente momento creativo que sigue viviendo la escena del Sinclair QL, donde la comunidad continúa desarrollando nuevo hardware y manteniendo viva una plataforma con más de cuatro décadas de historia a sus espaldas.</p>



<p class="wp-block-paragraph">Badaman nos trajo varios&nbsp;QLs en los que se estuvieron haciendo varias reparaciones. En uno de ellos se disponía un pieza muy útil, un multirom, basada en la ROM estándar 27C512 instalada en un zócalo. Lleva un conmutador externo con una memoria ROM de 512 KB, lo que permite tener 8 imágenes ROM. El selector de ROM tiene un interruptor de 4 posiciones, 3 de ellas para seleccionar la imagen de la ROM, y el último para activar o no el Toolkit 2. Esta selección se puede hacer sin abrir la máquina, desde la parte de atŕas del QL. Y como almacenamiento interno un QubIDE con ampliación de memoria de José Leandro. Otro equipo para reparar era un QLUB, un aparato para conectar por los puertos de red de QL a un PC con un emulador QL. Badaman es el artífice de la recuperación de material del Club Qlave que recopiló Salvador Merino, posteriormente publicado en la web <a href="https://sinclairql.es/?utm_source=chatgpt.com">Sinclair QL Recursos en Castellano</a>, y quien dio origen al grupo de amigos que participamos en este evento.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="410" data-attachment-id="4211" data-permalink="https://sinclairqles.wordpress.com/reparaciones_01/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg" data-orig-size="2040,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="reparaciones_01" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=545" alt="" class="wp-image-4211" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/reparaciones_01.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">DFSantos nos deleitó con su QL y un&nbsp;&nbsp;Spectrum&nbsp;Next, para el cual existe un core de QL muy logrado que pudimos ver allí. Nos presentó su libro “Inteligencia Artificial para el Z80: Si puedes construirlo en 8 bits, realmente lo entiendes” una nueva obra que propone mirar al pasado para entender el futuro. Escrito por <em>José Daniel Fernández Santos</em>, el libro plantea una idea tan simple como fascinante: aprender inteligencia artificial implementando sus conceptos directamente sobre un procesador Z80, el mismo corazón que latía dentro de máquinas tan nostálgicas como el ZX Spectrum, MSX o Amstrad CPC.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="410" data-attachment-id="4224" data-permalink="https://sinclairqles.wordpress.com/libro_dani/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg" data-orig-size="2040,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Libro_Dani" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=545" alt="" class="wp-image-4224" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/libro_dani.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">La participación de Salvador Merino fue de los momentos destacados del encuentro. Salvador representa la trayectoria dentro de la escena QL española y su papel al frente del Club Español Independiente de Usuarios de Sinclair QL (CEIUQL), conocido como QLave, que mantuvo activo desde Málaga entre 1987 y 1997. El club ayudó a numerosos usuarios en una época en la que conseguir información y software para el QL era especialmente difícil en España. Salvador también compartió sus experiencias en el mundo QL, reafirmando la importancia histórica y técnica que el Sinclair QL sigue teniendo para su comunidad de usuarios. En esta ocasión llevó al evento su caja y teclado profesional SPEM, un kit italiano que se vendía para hacer que el QL fuera más cómodo de usar.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="408" data-attachment-id="4212" data-permalink="https://sinclairqles.wordpress.com/spem02/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg" data-orig-size="2048,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="SPEM02" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=545" alt="" class="wp-image-4212" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/spem02.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">La mesa de Miguel Angel Rojo tenia un completo Sinclair QL equipado con una expansión TDI, ampliación de memoria, controladora Trump. Como sistema de almacenamiento masivo una QIMSI, en la que se utilizan archivos con extensión .WIN, también conserva una unidad de microdrive como mdv2_ y otra unidad MicroPicoDrive que la utiliza como mdv1_, un elemento cargado de nostalgia que sigue siendo imprescindible para los aficionados que nos gusta usar este medio de almacenamiento.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="419" data-attachment-id="4207" data-permalink="https://sinclairqles.wordpress.com/miguel_angel_02/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg" data-orig-size="917,706" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Miguel_Angel_02" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=545" alt="" class="wp-image-4207" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/miguel_angel_02.jpg 917w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Durante la jornada se realizaron diversas pruebas utilizando QLUB conectado a un portátil que ejecutaba el emulador QemuLator. Gracias al puerto QLnet fue posible transferir archivos entre ambos sistemas mediante un sencillo cable de audio mono, demostrando una vez más la versatilidad y actualidad del ecosistema QL.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="314" data-attachment-id="4210" data-permalink="https://sinclairqles.wordpress.com/qlub/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg" data-orig-size="800,461" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="QLUB" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=545" alt="" class="wp-image-4210" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/qlub.jpg 800w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Entre otros, también estuvo presente nuestro anfitrión, Carlos Izquierdo,&nbsp;que nos hizo de guía con su gran amabilidad al tremendo recinto del Museo y su impresionante colección de ordenadores de casi todas las décadas. Con él hicimos un viaje por la historia de la computación a través de más de 800 computadoras con las que cuenta la exposición. Seguramente la mayor colección de ordenadores de toda España. Algunos ordenadores fueron encendidos en esa visita. Pudimos ver computadoras de gran escala como supercomputadoras y mainframes, pasando por minicomputadoras, microcomputadoras, PCs, estaciones de trabajo y finalmente una sección con más de 100 videoconsolas.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="340" data-attachment-id="4222" data-permalink="https://sinclairqles.wordpress.com/museo/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg" data-orig-size="640,400" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="museo" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg?w=545" alt="" class="wp-image-4222" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/museo.jpg 640w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<p class="wp-block-paragraph">Se dejaron caer algunos asistentes, que fueron bienvenidos&nbsp;y pudieron experimentar de cerca lo que es un QL&nbsp;en su máximo esplendor.&nbsp;También estuvieron presentes los amigos del museo que nos acompañan en cada evento.</p>



<p class="wp-block-paragraph">En definitiva, la experiencia fue extraordinariamente positiva y nos deja con el deseo de repetir el próximo año. Una vez más, quedó patente que los entusiastas de la Sinclair QL continuamos manteniendo viva la pasión por esta máquina, demostrando día a día nuestra fidelidad e implicación, pese a las dificultades y al desafortunado devenir que marcó parte de su historia.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="545" height="408" data-attachment-id="4214" data-permalink="https://sinclairqles.wordpress.com/titox_qls_02/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg" data-orig-size="2048,1536" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Titox_QLs_02" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=584" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=545" alt="" class="wp-image-4214" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=545 545w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=1090 1090w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=300 300w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=768 768w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/05/titox_qls_02.jpg?w=1024 1024w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<h2 class="wp-block-heading">Vídeos y fotos del evento</h2>



<ul class="wp-block-list">
<li>Visita la galería de fotos <a href="https://badaman.badared.com/ql/galeria/?dir=RU-MHC-2026">aquí</a>.</li>



<li>Ver el video realizado por el Museo de Historia de la Computación <a href="https://www.youtube.com/watch?v=RSZUJ1cIcQo">aquí</a>.</li>



<li>Ver el video elaborado por napsternds <a href="https://www.youtube.com/watch?v=YegUhkMaryM">aquí</a>.</li>
</ul>



<p class="wp-block-paragraph"></p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: Cambio de idea de los temas especiales</title>
      <link>https://old8bits.blogspot.com/2026/05/zx2sb-cambio-de-idea-de-los-temas.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/05/zx2sb-cambio-de-idea-de-los-temas.html</guid>
      <pubDate>Mon, 04 May 2026 08:49:32 -0000</pubDate>
      <description><![CDATA[<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p><p style="text-align: center;"><span style="color: red;">Modificado el 04/05/2026, cambios en rojo&nbsp;</span></p>
<hr />
<h2 id="casos-especiales">Casos especiales: GO TO / GO SUB y variables con espacios</h2>

<div style="text-align: left;">
Había decidido tratar estos casos en el parser por comodidad, pero al final hacer las cosas de forma correcta es lo adecuado. En lugar de introducir excepciones en el parser, es mejor que el lexer los trate correctamente desde el principio y devuelva ya los tokens adecuados.
</div>

<h3>GO TO y GO SUB</h3>

<p>
Además de poder escribirse en una o dos palabras, he detectado que en un Spectrum +2/+3 (ya que en el «gomas» todo va por tokens y no es posible escribir otra cosa) se puede introducir <code>GOTO20</code> y el sistema lo separa correctamente como <code>GO TO 20</code>.
</p>

<p>
Ante esto, he modificado el lexer para que, cuando reciba una sentencia de salto, la trate correctamente en todas sus variantes.
</p>

<p>
De esta forma, las sentencias <code>GO TO 20</code>, <code>GOTO 20</code>, <code>GO TO20</code> o <code>GOTO20</code> generan siempre los mismos dos tokens:
</p>

<ul>
  <li><code>TK_GOTO</code></li>
  <li><code>TK_NUMERO 20</code></li>
</ul>

<p>
Y lo mismo ocurre con <code>GOSUB</code>, generando correctamente <code>TK_GOSUB</code> y <code>TK_NUMERO</code>.
</p>

<h3>Nombres de variables con espacios</h3>

<p>
Realmente no he visto ningún programa que utilice nombres de variables con espacios. Por tanto, he optado por simplificar el caso: si se detectan espacios en un nombre de variable, se eliminan directamente, generando un nombre compacto.
</p>

<p>
Así, cuando recibo:
</p>

<p><code>LET ANTES O DESPUES = 4</code></p>

<p>
El lexer genera directamente:
</p><ul>
  <li><code>TK_LET</code></li>
  <li><code>TK_VARIABLE antesodespues</code></li>
</ul><h4 style="text-align: left;"><span style="color: red;">Procesado en dos fases</span></h4>

<p><span style="color: red;">
Para realizar este cambio, tras buscar distintas alternativas durante la
 lectura del código y comprobar que el manejo directo se complicaba 
innecesariamente —especialmente por la coexistencia de palabras 
reservadas que no admiten espacios y nombres de variables que sí pueden 
contenerlos—, opté por una solución en dos fases.
</span></p>

<p><span style="color: red;">
El caso de variables con espacios será poco habitual, pero existe, y 
preferí resolverlo de forma explícita en lugar de cargar al parser con 
lógica adicional para anticipar combinaciones poco frecuentes.&nbsp;</span></p><p><span style="color: red;">&nbsp;En la <b>primera fase</b> se realiza únicamente el proceso de tokenización. Durante esta etapa, si se detecta que existen nombres de variables separados por espacios, que generan varios tokens consecutivos, al finalizar el proceso del Lexer se pregunta al usuario si desea procesarlos.
</span></p>

<p><span style="color: red;">
En la <b>segunda fase</b>, se reconstruyen esos nombres de variable, uniendo los tokens que originalmente estaban separados por espacios en un único identificador sin ellos, verificando previamente que el nombre resultante no coincida con ninguna palabra reservada del lenguaje.
</span></p>

<p><span style="color: red;">
Por ejemplo, ante una secuencia de tokens como:
</span></p>

<pre style="margin-left: 40px; text-align: left;"><span style="color: red;">
TK_LET
TK_VARIABLE antes
TK_VARIABLE o
TK_VARIABLE despues
</span></pre>

<p><span style="color: red;">Se genera:</span></p>

<pre style="margin-left: 40px; text-align: left;"><span style="color: red;">
TK_LET
TK_VARIABLE antesodespues
</span></pre>

<h4 style="text-align: left;"><span style="color: red;">Ficheros intermedios</span></h4>

<p><span style="color: red;">
Este proceso genera dos ficheros intermedios:
</span></p>

<ul>
  <li><span style="color: red;"><b>.TOK</b>, que contiene la tokenización inicial</span></li>
  <li><span style="color: red;"><b>.TOS</b>, que contiene los identificadores ya normalizados</span></li>
</ul>

<p><span style="color: red;">
De este modo, el parser puede elegir directamente cuál de los dos ficheros debe procesar, sin necesidad de realizar comprobaciones adicionales ni de modificar el mismo fichero en varias pasadas.
</span></p>

<h4 style="text-align: left;"><span style="color: red;">Separación de responsabilidades</span></h4>

<p><span style="color: red;">
Esta decisión refuerza una separación clara de responsabilidades dentro del diseño del sistema:
</span></p>

<ul>
  <li><span style="color: red;"><b>El lexer</b> se encarga exclusivamente de analizar el texto y generar tokens.</span></li>
  <li><span style="color: red;"><b>La normalización de identificadores</b> se realiza como una fase independiente, consciente y explícita.</span></li>
  <li><span style="color: red;"><b>El parser</b> puede centrarse únicamente en la estructura sintáctica, sin necesidad de anticipar ni corregir decisiones tomadas en fases anteriores.</span></li>
</ul>

<p><span style="color: red;">
Evitar que el parser tenga que buscar patrones por adelantado o reinterpretar secuencias ambiguas simplifica enormemente su implementación y lo hace más robusto y mantenible.
</span></p>

<h4 style="text-align: left;"><span style="color: red;">Pensando en el transpilador en SuperBASIC</span></h4>

<p><span style="color: red;">
Esta estrategia resulta especialmente útil de cara al futuro transpilador desarrollado en&nbsp;<b>SuperBASIC</b>, que no destaca precisamente por su facilidad para manejar ficheros. Generar dos ficheros independientes, en lugar de leer y modificar uno sobre la marcha, simplifica mucho la implementación y reduce el riesgo de errores.
</span></p>

<p><span style="color: red;">
En definitiva, se trata de priorizar una solución sencilla, explícita y robusta: cuanta menos lógica compleja se introduzca en las fases críticas del proceso, más fácil será mantener y evolucionar el sistema a largo plazo.
</span></p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: Mejorando el sistema de evaluación de expresiones y cambios menores</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-mejorando-el-sistema-de.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-mejorando-el-sistema-de.html</guid>
      <pubDate>Fri, 01 May 2026 10:26:01 -0000</pubDate>
      <description><![CDATA[<!--SEO recomendado para Blogger-->
<meta content="Mejoras en un transpilador de ZX BASIC: IR estructurado, parser avanzado, polaco inverso y resolución de casos especiales como GO TO y variables con espacios." name="description"></meta>
<meta content="ZX Spectrum, BASIC, transpilador, compilador, lexer, parser, polaco inverso, RPN, SuperBASIC" name="keywords"></meta>

<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p><p style="text-align: center;"><span style="color: red;">Cambios el 01/05/26 marcados en rojo&nbsp;</span></p>
<hr />
<h1>Mejorando el transpilador: IR estructurado y parser avanzado</h1>

<p>
En el desarrollo de un compilador no solo importa que el código “funcione”, sino que
cada fase esté bien delimitada y tenga responsabilidades claras. En este artículo
repasamos una mejora importante en el proyecto actual del transpilador de ZX BASIC:
una revisión de la arquitectura general, la resolución de algunos retos léxicos
concretos del lenguaje y, sobre todo, un cambio estructural en el parser que impacta
directamente en el análisis semántico y en la generación de código.
</p>

<hr />

<h2>Índice</h2>
<ul>
  <li><a href="#arquitectura">1. Arquitectura del compilador</a></li>
  <li><a href="#casos-especiales">2. Casos especiales del ZX BASIC</a></li>
  <li><a href="#estructuras">3. IR estructurado, PRINT y polaco inverso</a></li>
</ul>

<hr />

<h2 id="arquitectura">1. Arquitectura del compilador: lexer, parser, semántico y generador</h2>

<p>
Los compiladores suelen dividirse en cuatro fases clásicas, cada una con un papel bien definido.
Esta separación es fundamental para mantener el código extensible, depurable y preparado
para futuros backends.
</p>

<h3>Lexer (analizador léxico)</h3>

<p>
El lexer es la primera fase del proceso. Su tarea es transformar el texto fuente en una
secuencia de <i>tokens</i>: identificadores, números, palabras clave, operadores y signos
de puntuación. En esta fase se ignoran detalles como espacios irrelevantes o comentarios y
se normaliza el formato.
</p>

<p>
Una decisión clave del diseño es que el lexer produzca <b>tokens semánticamente completos</b>.
Esto evita que el parser tenga que reinterpretar combinaciones de palabras o resolver
ambigüedades que no le corresponden.
</p>

<p>
El lexer solo conoce los elementos básicos del lenguaje: separadores, delimitadores y
palabras reservadas. Un tema importante que se resuelve aquí es el de los comentarios,
que pueden variar desde el simple <code>REM</code> del BASIC clásico hasta los de estilo C de una línea&nbsp;<code>//</code> o de várias líneas&nbsp;<code>/* */</code>.
</p>

<p>
Habitualmente los comentarios se eliminan en esta fase, pero en nuestro caso se conservan ya que el objetivo es un transpilador en el que puedan mantenerse o descartarse a elección.
</p>

<h3>Parser (analizador sintáctico)</h3>

<p>
El parser recibe la secuencia de tokens y la interpreta según la gramática del lenguaje. El parser trabaja por sentencias completas de una o varias líneas, lo que en nuestro caso se simplifica ya que el BASIC se estructura por líneas, por lo que nuestro parser trabaja línea por línea, detectando sentencias, expresiones y separadores
como <code>:</code> o el fin de línea.
</p>

<p>
El resultado del parser debería ser un árbol sintáctico, pero he optado por una forma
simplificada: una representación intermedia (IR) estructurada, que conserva la forma
del programa pero es más fácil de analizar y transformar.
</p>

<p>
Inicialmente el parser emitía sentencias “ajustadas” como texto, lo que obligaba al
semántico y al generador a volver a analizarlas. Esto se ha corregido parcialemnte emitiendo ya
tokens y estructuras explícitas desde el parser.
</p>

<p>
El parser detecta errores estructurales (por ejemplo, un <code>LET</code> sin un <code>=</code>),
pero no valida aún el significado profundo del programa.
</p>

<h3>Semántico</h3>

<p>
El análisis semántico valida el significado del programa: existencia de variables, tipos,
uso correcto de arrays, coherencia de expresiones y saltos válidos. Aquí ya no importa
cómo estaba escrito el código original, sino qué representa.
</p>

<p>
Esta fase se beneficia enormemente de que el parser proporcione estructuras claras y no
cadenas ambiguas.
</p>

<h3>Generador de código</h3>

<p>
Por último, el generador traduce la IR validada a un backend concreto, como SuperBASIC o
ensamblador 68000. Al trabajar sobre estructuras bien definidas, puede centrarse únicamente
en cómo emitir el código.
</p>

<hr />

<h2 id="casos-especiales">2. Resolviendo casos especiales: GO TO / GO SUB y variables con espacios</h2><h2 id="casos-especiales"></h2>
<p><span style="color: red;">He cambiado ligeramente esto, ver la entrada <a href="https://old8bits.blogspot.com/2026/05/zx2sb-cambio-de-idea-de-los-temas.html">Cambio de idea en los temas especiales</a>, la idea es la misma solo cambia la resolución.</span></p><h3>GO TO y GO SUB</h3>

<p>
El ZX BASIC tiene particularidades históricas heredadas del ZX80/81, máquinas con 1Kb de memoria, por lo que ahorrar espacio era fundamental. 
  En el Spectrum un tema llamativos es el uso de
sentencias formadas por dos palabras:
</p>

<ul>
  <li><code>GO TO</code> en lugar de <code>GOTO</code></li>
  <li><code>GO SUB</code> en lugar de <code>GOSUB</code></li>
</ul>

<p>Esto se produce porque heradado de los ZX80/81, internamente el Spectrum no almacena cadenas de texto para las palabras reservadas, sino
códigos dentro del propio juego de caracteres. Por ejemplo, al ejecutar
<b>PRINT CHR$(236)</b> aparece en pantalla <b>GO TO</b>, lo que hace que internamente sea un solo código, pero se presente en pantalla como 5 caracteres.</p>

<p>
Aunque en los listados pueda verse separado, semánticamente siempre es una única
sentencia. Para tratarlo correctamente y aceptar las dos formas de escribir la sentencia, el lexer emite tokens separados para TK_GOTO, TK_GOSUB que ya se tratan sin problemas, y además para TK_GO, TK_TO, TK_SUB, y he optado por resolver este segundo caso en el parser por comodidad:
</p>

<ul style="text-align: left;">
  <li>Si aparecen&nbsp;<code>TK_GOTO o TK_GOSUB</code>, se aceptan directamente</li><li>Si aparece <code>TK_GO</code>, se mira el token siguiente</li>
  <ul><li>Si es <code>TK_TO</code>, se genera un único token <code>TK_GOTO</code>&nbsp;uniendo ambos tokens.</li>
  <li>Si es <code>TK_SUB</code>, se genera <code>TK_GOSUB</code>&nbsp;uniendo ambos tokens.</li>
  <li>Cualquier otro caso es un error.</li></ul>
</ul>

<p>
Así, el resto del parser y el semántico trabajan siempre con sentencias completas sin ambiguedades, y aunque parezca un poco inconsistente usar el mismo token para dos cosas, de esta manera tampoco hay conflictos con el TO usando en el bucle FOR.</p>

<h3>Nombres de variables con espacios</h3>

<p>
Otra particularidad del ZX Spectrum es que los nombres de las variables pueden contener
espacios, los cuales son ignorados por el intérprete. Así, <code>A B</code> es equivalente a
<code>AB</code>. Este comportamiento proviene del modo en que el analizador de sentencias elimina
los espacios considerados no significativos durante el análisis. No obstante, en mi
opinión esto no responde a una decisión de diseño consciente, sino más bien a un
<i>bug</i> del intérprete que no contempla correctamente este caso.
</p>

<p>
No he encontrado documentación oficial que lo acredite explícitamente como un bug, aunque
no soy el único que lo interpreta así, como puede verse en este comentario técnico:
<a href="https://atornblad.se/spaces-in-variable-names-in-zx-spectrum-basic/">
los nombres de variables pueden contener
espacios
</a>.
</p>

<p>
Como curiosidad histórica, en el lenguaje <b>FORTRAN</b> los espacios tampoco son significativos
en ningún punto de la sentencia. Esto daba lugar a ambigüedades bien conocidas, como el
clásico ejemplo:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>DO10I=1,10
</pre>

<p>
donde el compilador no puede distinguir, hasta encontrar la coma, si se trata de una
asignación:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>DO10I = 1
</pre>

<p>
o de un bucle:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>DO 10 I = 1,10
</pre>

<p>En ZX BASIC este comportamiento permite construcciones como esta&nbsp;que funcionan correctamente y muestran un <code>4</code> en pantalla:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>LET A B=4 : PRINT AB
</pre>

<p></p>

<p>
Para evitar estas ambigüedades en el transpilador, el lexer separa por espacios y produce varios identificadores en este caso, uno para A y otro para B, luego en el <b>parser</b> se detecta que existen varios&nbsp; identificadores consecutivos y se unen en uno solo, sustituyendo los espacios por un guion
bajo (<code>_</code>) y generando la variable A_B. Como este carácter no pertenece al juego de caracteres del Spectrum, no
puede entrar en conflicto con el código original. De este modo, el parser emite
internamente:
</p>

<pre>LET A_B=4 : PRINT AB
</pre>

<p>
El <b>semántico</b> considera equivalentes <code>A_B</code> y <code>AB</code>, y finalmente el
<b>generador</b> emite el nombre correcto para el backend, produciendo:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>LET AB=4 : PRINT AB
</pre>

<p>
sin ambigüedades y totalmente compatible con SuperBASIC.</p><hr />

<h2 id="estructuras">3. IR estructurado, PRINT dividido y polaco inverso</h2>

<p>
No me resultaba satisfactorio que el parser enviara información que luego el semántico
debía volver a analizar. Por ello he cambiado la representación de expresiones y PRINT,
acercándola más a un árbol real, pero usando una forma lineal conocida como <b>montón</b>
(heap), que corresponde al recorrido en postorden del árbol.
</p>

<h3>División estructurada del PRINT</h3>

<p>
En versiones anteriores del transpilador, el <code>PRINT</code> se generaba como texto
concatenado, lo que obligaba al semántico y al generador a reinterpretar de nuevo su
contenido. Esto resultaba frágil y poco extensible. Para evitarlo, ahora el parser
divide cada <code>PRINT</code> complejo en una secuencia de elementos estructurados.
</p>

<p>
Cada elemento del <code>PRINT</code> se representa como una unidad independiente que contiene:
</p>

<ul>
  <li>El <b>tipo</b> del elemento (cadena, variable, AT, TAB, INK, etc.)</li>
  <li>El <b>separador asociado</b> (<code>;</code>, <code>,</code> o ninguno)</li>
  <li>El <b>valor completo</b> del elemento</li>
</ul>

<p>
Por ejemplo, la siguiente línea en ZX BASIC:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>PRINT AT 3,4,"Nombre: ";N$;TAB(20);INK 3;"Edad: ";EDAD
</pre>

<p>
Es descompuesta por el parser en una serie de instrucciones <code>PRINT</code> independientes,
cada una representando un único elemento lógico:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;AT&gt;        &lt;3,4&gt;        &lt;,&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;CADENA&gt;    &lt;"Nombre: "&gt; &lt;;&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;VARIABLE&gt;  &lt;N$&gt;         &lt;;&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;TAB&gt;       &lt;(20)&gt;       &lt;;&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;INK&gt;       &lt;3&gt;          &lt;;&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;CADENA&gt;    &lt;"Edad: "&gt;   &lt;;&gt;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT &lt;VARIABLE&gt;  &lt;EDAD&gt;       &lt;none&gt;
</pre><p>
Esta representación intermedia permite que cada fase posterior trate los elementos de
forma adecuada, sin necesidad de volver a analizar texto.Durante la generación de código para SuperBASIC, estas instrucciones se traducen en:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>AT 3,4            <!--AT ya no forma parte del PRINT en SuperBASIC-->
<span>&nbsp;&nbsp; &nbsp;</span>PRINT ,           <!--La coma original debe preservarse-->
<span>&nbsp;&nbsp; &nbsp;</span>PRINT N$;
<span>&nbsp;&nbsp; &nbsp;</span>PRINT TO 20;      <!--TAB se convierte en TO-->
<span>&nbsp;&nbsp; &nbsp;</span>INK 3             <!--Modificadores gráficos salen del PRINT-->
<span>&nbsp;&nbsp; &nbsp;</span>PRINT "Edad: ";
<span>&nbsp;&nbsp; &nbsp;</span>PRINT EDAD
</pre>

<p>Hay varios cambios en SuperBASIC que obligan a hacerlo de esta manera:</p>
<ul style="text-align: left;">
  <li>AT ya no forma parte del PRINT, se debe lanzar por separado</li>
  <li>Los modificadores gráficos (<code>INK, PAPER,&nbsp;</code>etc.) tampoco se pueden usar ya dentro de un&nbsp;<code>PRINT</code>&nbsp;</li>
  <li>TAB cambia el nombre por TO, y no se deben usar paréntesis en su parámetro (en el Spectrum se puede usar con o sin paréntesis)&nbsp;</li>
  <li>Los separadores se tratan siempre correctamente.&nbsp;La segunda línea generada parece estraña, pero reemplaza a la coma tras el AT en la sentencia original, que ya no forma parte del 
	  PRINT. Pero la coma es necesario incluirla para mantener lo que intenta presentar en pantalla el programa original. Como usar una coma tras un AT o tras un TAB no tiene 
      sentido, el parser emite un warning de aviso únicamente.</li></ul>

<p>El resultado es un código más limpio, correcto y fácil de
generar, sin alterar para nada el código original, solo adaptado a la forma de trabajar del SuperBASIC.</p>

<h3>Expresiones en polaco inverso (RPN), árbol y montón</h3>

<p>
Hasta ahora, el parser analizaba las expresiones, pero luego las enviaba prácticamente
en bruto al semántico, que debía volver a analizarlas. Esto implicaba repetir trabajo y
hacía el sistema más frágil. Para evitarlo, el parser genera ahora una representación
estructurada de las expresiones basada en el recorrido del árbol sintáctico.
</p>

<p>
Internamente, toda expresión puede representarse como un <b>árbol</b>, donde los nodos
son operadores y las hojas son operandos (variables, literales, constantes, etc.). Por
ejemplo, la expresión:
</p>

<pre>A + B * C
</pre>

<p>
se puede representar mediante el siguiente árbol:
</p>

<pre>    +
   / \
  A   *
     / \
    B   C
</pre>

<p>
En lugar de almacenar explícitamente el árbol, el parser lo recorre en <b>postorden</b>
(primero los hijos, luego el nodo) y guarda el resultado en una estructura lineal
denominada <b>montón</b> (heap). Esta representación corresponde a lo que se conoce como
<b>notación polaca inversa</b> o <b>RPN</b> (<i>Reverse Polish Notation</i>):
</p>

<pre>A B C * +
</pre>

<p>
El montón es, por tanto, un árbol almacenado de forma lineal: conserva toda la
información estructural, pero sin necesidad de punteros ni referencias explícitas,
lo que facilita enormemente su almacenamiento y recorrido.
</p>

<p>
La evaluación de una expresión en RPN se realiza mediante un <b>autómata de pila</b>,
siguiendo una regla muy simple: cuando se lee un operando se apila, y cuando se lee un
operador se desapilan los dos elementos superiores, se aplica la operación y se vuelve
a apilar el resultado.
</p>

<p>
Siguiendo el ejemplo anterior, la evaluación de <code>A B C * +</code> se realiza así:
</p>

<pre>Lee A  -&gt; se apila A                (A)
Lee B  -&gt; se apila B                (B, A)
Lee C  -&gt; se apila C                (C, B, A)
Lee *  -&gt; C * B, se apila resultado (C*B, A)
Lee +  -&gt; (C*B) + A                 (resultado final)
</pre>

<p>
La gran ventaja de este enfoque es que ya no son necesarios paréntesis ni reglas
implícitas de precedencia durante la evaluación: el orden de las operaciones está
completamente determinado por la posición de los elementos en el montón. La única
responsabilidad del parser es generar correctamente el árbol y recorrerlo respetando
la prioridad de los operadores.
</p>

<p>
Gracias a esta representación, el <b>semántico</b> puede validar fácilmente las
expresiones (tipos, operaciones permitidas, número de operandos), y el <b>generador</b>
puede emitir código eficiente sin necesidad de reinterpretar la expresión original.
</p>


<h3>Impacto en el semántico y el generador</h3>

<ul>
  <li>El semántico trabaja directamente con estructuras, sin reinterpretar texto.</li>
  <li>El generador emite código más limpio y eficiente.</li>
</ul>

<p>
Este enfoque hace que el transpilador sea más robusto, más extensible y más preparado
para múltiples backends.
</p>

<hr />

<p>
Este tipo de decisiones estructurales, aunque no siempre visibles desde fuera, son las
que marcan la diferencia entre un traductor funcional y un compilador sólido y
mantenible (y sí, también sirven de consuelo cuando uno se da cuenta de que ha tenido
que volver a&nbsp; rehacer parte del camino por no preveerlo desde el inicio).
</p>
<p></p><p></p>]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Conversor de Basic de los ZX al Super Basic del QL. Indice</title>
      <link>https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html</guid>
      <pubDate>Fri, 01 May 2026 10:21:59 -0000</pubDate>
      <description><![CDATA[<h3>Segundo intento de desarrollo, a ver si esta vez se completa</h3>
<p>Este proceso está muy desordenado, ya que he ido alante y atrás todo el rato cambiando el planteamiento de las cosas varias veces, además de ir añadiendo mejoras y simplificando temas, pero es lo que hay cuando no haces un estudio completo antes de empezar,
    ir programando sobre la marcha tiene estos temas.</p><p>
<br />
    01. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-conversor-de-basic-de-los-zx-al.html">ZB2SB. Conversor de Basic de los ZX al Super Basic del QL (Reinicio)</a><br />
    02. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-paso-1-definir-el-lenguaje-de.html">Definir el Lenguaje de origen</a><br />
    03. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-paso-2-director-del-proyecto.html">DIRECTOR DEL PROCESO</a><br />
    04. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-paso-auxiliar-1-estructuras-de.html">Estructuras de datos </a><br />
    05. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-paso-3-lexer-estructura-de-datos.html">LEXER. ANALIZADOR LÉXICO. Estructuras de datos para el Lexer</a><br />
    06. <a href="https://old8bits.blogspot.com/2026/03/zb2sb-paso-32-lexer-planteamiento.html">LEXER. ANALIZADOR LÉXICO. Planteamiento</a><br />
    07. <a href="https://old8bits.blogspot.com/2026/04/zx2sb-vuelta-empezar.html ">Un nuevo comienzo</a><br />
    08. <a href="https://old8bits.blogspot.com/2026/04/zx2sb-el-parser-o-analizador-sintactico.html">PARSER. Analizador semántico</a><br />
    09. <a href="https://old8bits.blogspot.com/2026/04/zx2sb-el-generador-de-codigo-superbasic.html">GENERADOR de código en SuperBASIC&nbsp;</a><br />
    10. <a href="https://old8bits.blogspot.com/2026/04/zx2sb-otra-vuelta-atras.html ">Otra vuelta atrás (y van...)</a><br />
	11. <a href="https://old8bits.blogspot.com/2026/04/zx2sb-mejorando-el-sistema-de.html">Mas mejoras (los cambios no acaban nunca)</a><br>
    12. <a href="https://old8bits.blogspot.com/2026/05/zx2sb-cambio-de-idea-de-los-temas.html">Cambio de idea en los temas especiales</a>

</p>
<p>&nbsp;</p>
<hr />
<h3>Primer intento de desarrollo, fallido, pero quizá te interese ver alguna cosa</h3>
<p>
    01. <a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al.html">Introducción</a>
    <br /> 02. <a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_12.html">Analizador Léxico primera parte: Lectura de líneas&nbsp;</a>
    <br /> 03. <a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_16.html">Analizador Léxico segunda parte: Extractor de Tokens</a></p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: Otra vuelta atrás</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-otra-vuelta-atras.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-otra-vuelta-atras.html</guid>
      <pubDate>Tue, 28 Apr 2026 09:53:51 -0000</pubDate>
      <description><![CDATA[<head>
  <meta charset="utf-8"></meta>

  <title>Rediseño del IR en ZX2SB: por qué abandonar el AST y conservar tokens semánticos</title>

  <meta content="Análisis del rediseño del IR en ZX2SB: del AST clásico a un IR lineal con tokens semánticos, motivaciones técnicas y ventajas prácticas para BASIC y SuperBASIC." name="description"></meta>

  <meta content="ZX Spectrum BASIC, SuperBASIC, IR, representación intermedia, AST, compiladores, tokenización, ZX2SB" name="keywords"></meta>

  <meta content="ZX2SB Project" name="author"></meta>
  <meta content="width=device-width, initial-scale=1.0" name="viewport"></meta>
</head>

<style>
a {
  text-decoration: none;
  color: #464feb;
}
tr th, tr td {
  border: 1px solid #e6e6e6;
}
tr th {
  background-color: #f5f5f5;
}
</style>
<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p>
<hr />

<h1>Otra vuelta atrás: IR con tokens</h1>

<p>
Como ha ocurrido otras veces durante el desarrollo del proyecto, cada vez que empiezo
a generar código válido aparece un problema no contemplado y me obliga a retroceder
un paso más. En este caso ha sido un tema de espacios excesivos.
</p>

<p>
No sirve de nada generar código como:
</p>

<pre>LET A ( C + D ) = G ( 3 * 4 )</pre>

<p>
en lugar de:
</p>

<pre>LET A(C+D)=G(3*4)</pre>

<p>
En una máquina con recursos limitados, cada carácter cuenta. Podría parecer sencillo
“eliminar espacios”, pero eso llevaría a errores como convertir:
</p>

<pre>IF A AND B THEN C = 3</pre>

<p>
en algo no válido como:
</p>

<pre>IF AANDB THEN C=3</pre>

<p>
Para usar únicamente los espacios imprescindibles es necesario identificar correctamente
palabras reservadas, variables, operadores y separadores. Eso implica que los tokens
deben estar disponibles en la fase correcta: el generador. Por simplificar el IR inicial,
no los incluí… y ahora solo quedaban dos opciones: volver a tokenizar en el generador
(lo cual es absurdo) o generar el IR conservando los tokens.
</p>

<p>
En esta entrada documento otro de los cambios arquitectónicos realizados en <b>ZX2SB</b>.
Tras decidir no usar un AST completo por su complejidad, pasé a un IR lineal simple.
Ahora el paso lógico es evolucionar hacia un <b>IR lineal basado en tokens semánticos</b>.
</p>

<p>
Aunque puede parecer otra “vuelta atrás”, en realidad es una decisión motivada por
la naturaleza del lenguaje BASIC, por la experiencia práctica adquirida durante el
desarrollo del transpilador y, sobre todo, por la necesidad de generar código SuperBASIC
limpio, editable y sin reconstrucciones artificiales.
</p>

<hr />

<h2 id="indice">Índice</h2>
<ul>
  <li><a href="#que-es-ir">¿Qué es el IR en ZX2SB?</a></li>
  <li><a href="#problema-ast">El problema del AST completo en BASIC</a></li>
  <li><a href="#decision">La decisión: IR lineal con tokens semánticos</a></li>
  <li><a href="#tokens">Tokens canónicos y eliminación de ambigüedad</a></li>
  <li><a href="#espacios">Espacios, formato y generación correcta</a></li>
  <li><a href="#ventajas">Ventajas prácticas del nuevo IR</a></li>
  <li><a href="#conclusion">Conclusión</a></li>
</ul>

<hr />

<h2 id="que-es-ir">¿Qué es el IR en ZX2SB?</h2>

<div style="font-family: &quot;Segoe UI&quot;; font-size: 14px; line-height: 20px; text-align: justify;">
<b>Nota auxiliar:</b>
Un <b>AST (Abstract Syntax Tree)</b> es una estructura de datos en forma de <b>árbol</b>
que representa la organización sintáctica de un programa tras el análisis del parser,
eliminando elementos puramente textuales y conservando únicamente su estructura semántica.
Cada nodo representa una construcción del lenguaje y se almacena normalmente en memoria
como una jerarquía de objetos, aunque también puede serializarse para depuración u otros
pasos intermedios del compilador.
</div>

<p>
En lenguajes más complejos y de estructura libre como C o Pascal, el AST es imprescindible.
BASIC, en cambio, es un lenguaje más sencillo y organizado por líneas, lo que permite simplificar
su representación interna.
</p>

<p>
En ZX2SB, el <b>IR (Intermediate Representation)</b> es la forma intermedia generada tras
el análisis sintáctico y semántico del ZX BASIC original, y sirve como base para:
</p>

<ul>
  <li>Renumeración de líneas</li>
  <li>Reescritura de saltos</li>
  <li>Optimización estructural</li>
  <li>Generación del código SuperBASIC final</li>
</ul>

<p>
El IR no es solo texto, pero tampoco es ejecutable: describe la intención del programa sin la
complejidad de manejar un árbol completo, y además se puede guardar fácilmente como
texto lineal.
</p>

<hr />

<h2 id="problema-ast">El problema del AST completo en BASIC</h2>

<p>
Un AST clásico es ideal para lenguajes donde la semántica está profundamente anidada y
la estructura es jerárquica.
</p>

<p>
BASIC, y en particular ZX BASIC, tiene características distintas:
</p>

<ul>
  <li>Sentencias lineales</li>
  <li>Control de flujo basado en números de línea</li>
  <li>Expresiones relativamente simples</li>
  <li>Poca recursividad sintáctica</li>
</ul>

<p>
Mantener un AST completo obligaba a aplanar el árbol para generar código, volver a decidir
espaciado y operadores, y en muchos casos reinferir información que ya se conocía.
</p>

<blockquote>
“¿Por qué tengo que volver a tokenizar o reinterpretar lo que yo mismo ya había parseado?”
</blockquote>

<p>
Durante un tiempo decidí continuar con el IR simple porque era “suficiente”, pero al final
la realidad es que los errores no se mantienen: se refactorizan.
</p>

<hr />

<h2 id="decision">La decisión: IR lineal con tokens semánticos</h2>

<p>
La solución adoptada es intermedia entre un AST completo y un IR puramente textual:
</p>

<p><b>Un IR lineal, basado en tokens tipados y semánticamente anotados.</b></p>

<p>
Cada sentencia BASIC se representa mediante:
</p>

<ul>
  <li>Un tipo de sentencia (IF, LET, FOR, PRINT…)</li>
  <li>Campos estructurales explícitos</li>
  <li>Expresiones como listas ordenadas de tokens</li>
</ul>

<p>
No existen árboles internos complejos ni se guardan nombres textuales de sentencias,
sino identificadores internos, lo que simplifica enormemente el trabajo posterior.
</p>

<hr />

<h2 id="tokens">Tokens canónicos y eliminación de ambigüedad</h2>

<p>
Uno de los problemas recurrentes era usar nombres distintos para el mismo token en
distintos módulos. Un simple <code>Par_Ape</code> frente a <code>ParenIzq</code> puede provocar
horas de depuración absurda.
</p>

<p>
La solución fue eliminar completamente:
</p>

<ul>
  <li>Nombres textuales de tokens</li>
  <li>Strings mágicos entre módulos</li>
  <li>Dependencias implícitas entre fases</li>
</ul>

<p>
En su lugar se introdujeron <b>TokenID canónicos</b>:
</p>

<ul>
  <li>Identificadores numéricos estables</li>
  <li>Definidos en un único enum</li>
  <li>Usados por lexer, parser, IR y generador</li>
</ul>

<p>
Ahora todo el sistema habla exactamente el mismo idioma.
</p>

<hr />

<h2 id="espacios">Espacios, formato y generación correcta</h2>

<p>
El nuevo IR resolvió definitivamente el problema del espaciado. La idea clave es simple:
</p>

<blockquote>
“No eliminamos espacios. Decidimos cuándo ponerlos.”
</blockquote>

<p>
Al conservar operadores, paréntesis y separadores como tokens reales, el generador puede
emitir código compacto o legible, sin ambigüedades y sin depender de parches, y evitando usar expresiones regulares, no se soportan en SuperBASIC y el objetivo final es portar el código de ZX2SB a SuperBASIC.</p>

<hr />

<h2 id="ventajas">Ventajas prácticas del nuevo IR</h2>

<ul>
  <li>No se recompone información ya conocida</li>
  <li>No se retokeniza texto generado</li>
  <li>El IR es imprimible y depurable</li>
  <li>El código SuperBASIC generado es editable</li>
  <li>El sistema escala sin hackear fases anteriores</li>
</ul>

<blockquote>
“El diseño deja de luchar contra el lenguaje y empieza a trabajar con él.”
</blockquote><p>Frase muy bonita que solo quiere decir que me auto animo a seguir adelante, a pesar de las peleas que tengo contínuamente con el código, es mi primer "compilado" completo y se nota.</p>

<hr />

<h2 id="conclusion">Conclusión</h2>

<p>
Abandonar un AST completo no significa perder rigor si el IR está bien diseñado. En ZX2SB
ha supuesto mayor coherencia, menos fases ficticias y un generador más simple y fiable.
</p>

<p>
Como suele ocurrir en ingeniería de lenguajes, la solución elegante no fue añadir más
estructura, sino <i>conservar solo la estructura que importa</i>.
</p>

<hr />

<p><i>ZX2SB Project (algún día será un proyecto serio; de momento es una fuente constante
de problemas… y un entretenimiento mental bastante divertido)</i></p>]]></description>
    </item>
    
    <item>
      <title>Pistorm para Sinclair QL</title>
      <link>https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/</link>
      <guid isPermaLink="true">https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/</guid>
      <pubDate>Thu, 23 Apr 2026 20:28:18 -0000</pubDate>
      <description><![CDATA[<p class="wp-block-paragraph">El usuario de QL Forum Will James lleva varios meses trabajando en una versión de Pistorm para Sinclair QL. Hoy ha hecho el anuncio de que ha completado el desarrollo:</p>



<p class="wp-block-paragraph"><a href="https://www.theqlforum.com/viewtopic.php?p=71508#p71508">https://www.theqlforum.com/viewtopic.php?p=71508#p71508</a></p>



<p class="wp-block-paragraph">Estará disponible en tres versiones, de las que hará públicos los archivos para que cualquiera pueda montársela:</p>



<p class="wp-block-paragraph">1) Expansion port Pi Zero 2<br>2) Expansion port CM4<br>3) Internal CM4</p>



<figure data-carousel-extra='{&quot;blog_id&quot;:6585465,&quot;permalink&quot;:&quot;https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/&quot;}'  class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" width="400" height="225" data-attachment-id="4194" data-permalink="https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/pizero2/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg" data-orig-size="400,225" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PiZero2" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg?w=400" data-id="4194" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg?w=400" alt="" class="wp-image-4194" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg 400w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/pizero2.jpg?w=300 300w" sizes="auto, (max-width: 400px) 100vw, 400px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" width="400" height="225" data-attachment-id="4193" data-permalink="https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/cm4_internal/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg" data-orig-size="400,225" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="CM4_Internal" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg?w=400" data-id="4193" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg?w=400" alt="" class="wp-image-4193" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg 400w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_internal.jpg?w=300 300w" sizes="auto, (max-width: 400px) 100vw, 400px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" width="400" height="225" data-attachment-id="4192" data-permalink="https://sinclairqles.wordpress.com/2026/04/23/pistorm-para-sinclair-ql/cm4_expansion_port/" data-orig-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg" data-orig-size="400,225" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="CM4_Expansion_Port" data-image-description="" data-image-caption="" data-large-file="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg?w=400" data-id="4192" src="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg?w=400" alt="" class="wp-image-4192" srcset="https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg 400w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg?w=150 150w, https://sinclairqles.wordpress.com/wp-content/uploads/2026/04/cm4_expansion_port.jpg?w=300 300w" sizes="auto, (max-width: 400px) 100vw, 400px" /></figure>
</figure>



<p class="wp-block-paragraph">Según los bechmarks que ha publicado, ha conseguido alcanzar un redimiento de 40x el QL estándar.</p>



<p class="wp-block-paragraph">Estaremos atentos a la disponibilidad para poder probarla y comentar nuestras impresiones.</p>



<p class="wp-block-paragraph">Una gran noticia para el QL!!!</p>



<p class="wp-block-paragraph"></p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: El generador de código SuperBASIC</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-el-generador-de-codigo-superbasic.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-el-generador-de-codigo-superbasic.html</guid>
      <pubDate>Mon, 20 Apr 2026 11:52:53 -0000</pubDate>
      <description><![CDATA[<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p>
<hr />
<h1>El generador en ZX2SB: diseño, decisiones y límites conscientes</h1>

<p>
En el proyecto <b>ZX2SB</b>, 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.
</p>

<p>
Este artículo describe qué hace realmente el generador, cómo está diseñado,
qué problemas resuelve y, sobre todo, qué problemas<b> decide&nbsp;conscientemente no resolver</b>.
Esa última decisión resulta clave para entender por qué ZX2SB va más allá
de un simple transpilador mecánico .
</p>

<hr />

<h2 id="indice">Índice</h2>
<ul>
  <li><a href="#generador">1. ¿Qué es el generador?</a></li>
  <li><a href="#estructura">2. Generación estructurada de código</a></li>
  <li><a href="#controlflujo">3. Numeración, control del flujo y bloques</a></li>
  <li><a href="#noportables">4. Instrucciones no portables</a></li>
  <li><a href="#fn">5. Convención FN_ y desacoplo</a></li>
  <li><a href="#inicializacion">6. Inicialización e inclusión de funciones FN_</a></li>
  <li><a href="#resultado">7. Qué se obtiene al final</a></li>
  <li><a href="#siguiente">8. El siguiente paso inmediato</a></li>
  <li><a href="#futuro">9. Más allá del generador</a></li>
</ul>

<hr />

<h2 id="generador">1. ¿Qué es el generador?</h2>

<p>
Conviene recordar una distinción fundamental:
un <b>compilador</b> genera un ejecutable,
mientras que un <b>transpilador</b> genera código fuente en otro lenguaje,
que puede ejecutarse directamente o servir como entrada para otro compilador.
</p>

<p>
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.
</p><p>
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 <b>reformula</b>.&nbsp;</p><p>NOTA:&nbsp;</p><div style="font-family: &quot;Segoe UI&quot;; font-size: 14px; font-style: normal; font-weight: 400; line-height: 20px;">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.</div>&nbsp;<p></p>

<div style="background: rgb(249, 249, 249); border: 1px solid rgb(204, 204, 204); margin: 20px 0px; padding: 15px;">
  <h3 style="margin-top: 0px;">Qué <b>no</b> es ZX2SB</h3>

  <p>
    ZX2SB <b>no es</b> un emulador del ZX Spectrum ni una herramienta para ejecutar
    directamente programas ZX en el Sinclair QL.
  </p>

  <p>
    Tampoco es un traductor mecánico línea a línea que intente forzar equivalencias
    entre dos BASIC muy distintos ignorando sus diferencias internas.
  </p>

  <p>
    ZX2SB es un <b>transpilador consciente</b>: 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.
  </p><p style="margin-bottom: 0px;">
    Su objetivo no es “que funcione como sea”, sino producir código que pueda
    entenderse, mantenerse y evolucionar en el entorno del QL.&nbsp;
</p></div>

<p>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.</p>
<hr />

<h2 id="estructura">2. Generación estructurada de código</h2>

<p>
Una de las primeras decisiones fue producir código SuperBASIC claramente
estructurado, incluso cuando el código ZX original no lo está explícitamente.</p>

<p>En ZX BASIC es habitual encontrar varias sentencias en la misma línea, separadas por dos puntos (:). Por ejemplo:</p>
<pre>PRINT A: LET B=B+1 : LET col=col+1</pre>
El generador transforma este tipo de líneas en una estructura explícita y mas legible:
<pre>  PRINT A
  LET B=B+1
  LET col=col+1
</pre>
<p>
Cada sentencia pasa a ocupar su propia línea. El caso más significativo es el <code>IF</code>.
En ZX BASIC no existen <code>ELSE</code> ni <code>END IF</code>,
y un IF puede contener múltiples sentencias en una sola línea.
El generador transforma esta construcción en un bloque explícito:
</p>

<ul>
  <li>una línea con la condición IF,</li>
  <li>una línea por cada sentencia interna,</li>
  <li>un <code>END IF</code> final.</li>
</ul>

De esta manera la senténcia <pre>IF A&gt;10: PRINT A: LET B=B+1</pre> genera:
<pre>IF A&gt;10 THEN
  PRINT A
  LET B=B+1
END IF
</pre>
<p>
Esta decisión no se tomó por comodidad,
sino para garantizar:&nbsp;</p><ul style="text-align: left;"><li>Claridad semántica</li><li>Coherencia estructural</li><li>Facilidad de mantenimiento</li><li>Posibilidad de posteriores optimizaciones.&nbsp;</li></ul><p><style>a {
    text-decoration: none;
    color: #464feb;
}tr th, tr td {
    border: 1px solid #e6e6e6;
}tr th {
    background-color: #f5f5f5;
}</style></p><div style="font-family: &quot;Segoe UI&quot;; font-size: 14px; font-style: normal; font-weight: 400; line-height: 20px;">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.</div>&nbsp;<p></p>

<hr />

<h2 id="controlflujo">3. Numeración, control del flujo y bloques</h2>

<p>
Para poder descomponer líneas múltiples en sentencias independientes,
el generador debe <b>modificar la numeración de las líneas</b>.
</p>

<p>
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.
</p>

<p>
Por ejemplo, a partir del código ZX:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>150 LET A=3 : LET C=5 : GOTO 532
</pre>

<p>
El generador produce:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>15000 LET A=3
<span>&nbsp;&nbsp; &nbsp;</span>15001 LET C=5
<span>&nbsp;&nbsp; &nbsp;</span>15002 GOTO 53200
</pre>

<p>
Estos números <b>no siempre son válidos para el QL</b> (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.
</p>

<p>
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.</p>

<p>El generador sí garantiza que:</p>
<ul><li>los saltos son coherentes</li>
  <li>los bloques se abren y cierran correctamente</li>
  <li>el programa resultante es estructuralmente consistente.</li></ul>

<p>Para el generador la numeración es solo una herramienta provisional. Se usa como andamio mientras se construye el programa final.</p>

<hr />

<h2 id="noportables">4. Instrucciones no portables</h2>

<p>
ZX BASIC y SuperBASIC difieren profundamente en áreas clave como:
</p>

<ul>
  <li>Salida de texto (PRINT)</li>
  <li>Gestión del cursor</li>
  <li>Colores y atributos</li>
  <li>Caracteres gráficos</li>
  <li>Sistema de coordenadas y gráficos</li>
</ul>

<p>
Una traducción directa de estas instrucciones produciría programas
que “funcionan”, pero cuyo comportamiento se aleja mucho
del ZX Spectrum original.
</p>

<p>
El generador asume explícitamente que estas instrucciones
<b>no son directamente portables</b>
y evita resolverlas de forma incorrecta.
</p>

<hr />

<h2 id="fn">5. Convención FN_ y desacoplo</h2>

<p>
Para manejar instrucciones sin equivalencia directa,
el generador adopta una convención clara y sistemática:
emitir llamadas a funciones o procedimientos con prefijo <code>FN_</code>.&nbsp;</p><p>El generador:&nbsp;</p>

<ul>
  <li><b>No implementa</b> su comportamiento</li>
  <li>Se limita a transformar el código</li>
  <li>La semántica se decide posteriormente</li>
</ul>

<p>
Por ejemplo, en lugar de generar directamente:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>BIN 11001101
</pre>

<p>como el comando <code>BIN</code> no existe en SuperBASIC,
el generador produce:
</p>

<pre><span>&nbsp;&nbsp; &nbsp;</span>FN_BIN(11001101)
</pre>

<p>
De este modo, el generador queda completamente desacoplado
de la implementación concreta de estas funciones,
manteniendo el sistema limpio y extensible.
</p>

<hr />

<h2 id="inicializacion">6. Inicialización e inclusión de funciones FN_</h2>

<p>Durante la generación, el sistema mantiene una lista de todas las funciones <b>FN_</b> que ha utilizado durante el proceso de conversión. Al finalizar la generación, <b>solo se incluyen aquellas funciones que han sido realmente necesarias</b>, evitando así añadir código innecesario al programa final.
</p>

<p>
Además, el programa resultante se completa con varias secciones fijas, que quedan organizadas de la siguiente manera:
</p>

<ol>
  <li>Un bloque de inicio del sistema, que realiza una llamada a la función <b>FN_INIT</b>.</li>
  <li>El código convertido desde ZX BASIC a SuperBASIC.</li>
  <li>Si es necesario, una línea <b>STOP</b> que marca explícitamente el final del programa ZX.</li>
  <li>Las funciones <b>FN_</b> que han sido detectadas como necesarias durante la generación.</li>
  <li>La implementación de<b> <code>FN_INIT</code></b></li>
</ol>

<p>
La función <b>FN_INIT</b> se encarga de preparar el entorno de ejecución en el QL: establece el modo de pantalla, crea una ventana de <b>32×24 caracteres</b> y aplica una configuración básica que permite que el programa se ejecute de forma coherente y predecible.</p>
<p>El <b>STOP </b>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&nbsp;<b>STOP</b>, cambiando los saltos que existan para que apunten a esta nueva línea. De esta manera:</p><ul style="text-align: left;"><li>El programa generado se vuelve más determinista y sencillo de manejar</li><li>Se evitan posibles problemas derivados de que un salto coincida con alguna de las líneas añadidas posteriormente para las funciones <b>FN</b></li></ul>

<hr />

<h2 id="resultado">7. Qué se obtiene al final</h2>

<p>
El resultado del generador es un programa SuperBASIC que:
</p>

<ul>
  <li>Es sintácticamente correcto</li>
  <li>Preserva la estructura lógica del ZX BASIC original</li>
  <li>No fuerza equivalencias incorrectas</li>
  <li>Explicita claramente las dependencias externas</li>
</ul>

<p>
Este programa está listo para ser renumerado, indentado
y ejecutado dentro de un entorno controlado.
</p>

<hr />

<h2 id="siguiente">8. El siguiente paso inmediato</h2><p>
El siguiente paso tras el generador es el <b>renumerador</b>,
que ajusta definitivamente la numeración y produce
un programa cargable y ejecutable en el QL.&nbsp;Con este proceso de renumerción se cierra la primera fase del proyecto.
</p>

<hr />

<h2 id="futuro">9. Más allá del generador</h2>

<p>
El verdadero reto futuro no está en el generador,
sino en lo que rodea a las llamadas <code>FN_...</code>.
</p>

<p>
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.
</p>

<p>
Ese componente convierte a ZX2SB en algo más que un transpilador,
pero merece un análisis independiente y se abordará
en otro momento.
</p>

<hr />

<p>
Continuará con el renumerador…
</p>]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Paso 3.2. Lexer. Planteamiento</title>
      <link>https://old8bits.blogspot.com/2026/03/zb2sb-paso-32-lexer-planteamiento.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zb2sb-paso-32-lexer-planteamiento.html</guid>
      <pubDate>Mon, 20 Apr 2026 11:52:04 -0000</pubDate>
      <description><![CDATA[<!--=========================================================
     CABECERA SEO — old8bits.blogspot.com
=========================================================-->

<title>Transpilador ZX BASIC — Paso 1: Analizador léxico (tokens, diagrama, seudo‑código y esqueleto VB/SuperBasic)</title>
<meta content="Primera fase del transpilador desde ZX BASIC: diseño del analizador léxico. Definición de tokens, conjunto de tokens objetivo, diagrama de bloques, seudo‑código optimizado para QL y esqueletos de implementación en VB/SuperBasic." name="description"></meta>

<!--Open Graph-->
<meta content="article" property="og:type"></meta>
<meta content="Transpilador ZX BASIC — Paso 1: Analizador léxico" property="og:title"></meta>
<meta content="Diseño del analizador léxico: qué es un token, qué tokens generaremos, diagrama del lexer, seudo‑código optimizado para QL y esqueletos en VB/SuperBasic." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com/2026/03/transpilador-zx-basic-analizador-lexico.html" property="og:url"></meta>
<meta content="old8bits" property="og:site_name"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" property="og:image"></meta>

<!--Twitter Cards-->
<meta content="summary_large_image" name="twitter:card"></meta>
<meta content="Transpilador ZX BASIC — Paso 1: Analizador léxico" name="twitter:title"></meta>
<meta content="Fase léxica: tokens, diagrama de bloques, seudo‑código y esqueletos de implementación." name="twitter:description"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" name="twitter:image"></meta>
<meta content="@old8bits" name="twitter:site"></meta>

<!--Datos estructurados (schema.org/TechArticle)-->
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@type":"TechArticle",
  "mainEntityOfPage":{"@type":"WebPage","@id":"https://old8bits.blogspot.com/2026/03/transpilador-zx-basic-analizador-lexico.html"},
  "headline":"Transpilador ZX BASIC — Paso 1: Analizador léxico",
  "description":"Definición y diseño del analizador léxico para ZX BASIC: tokens, diagrama del lexer, seudo‑código y esqueletos en VB/SuperBasic.",
  "image":["https://blogger.googleusercontent.com/IMAGEN_REEMPLAZAR.jpg"],
  "author":{"@type":"Person","name":"javu61"},
  "publisher":{"@type":"Organization","name":"old8bits","logo":{"@type":"ImageObject","url":"https://blogger.googleusercontent.com/LOGO_REEMPLAZAR.png","width":180,"height":180}},
  "datePublished":"2026-03-12T10:00:00+01:00",
  "dateModified":"2026-03-12T10:00:00+01:00",
  "keywords":["ZX Spectrum 48K","ZX BASIC","transpilador","analizador léxico","tokens","BNF","lexer","parser","retrocomputación"],
  "articleSection":"Programación"
}
</script>

<!--=========================================================
====== TABLA DE CONTENIDOS + ESTILOS
=========================================================-->
<style>
.toc-container {
  background: #eef4ff;
  border: 1px solid #c7d7f5;
  padding: 16px 18px;
  margin: 18px 0 28px 0;
  border-radius: 6px;
  font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px 0; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; list-style: disc; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }

code, pre {
  background: #f6f8fa;
  border: 1px solid #e1e4e8;
  padding: 10px;
  border-radius: 6px;
  display: block;
  white-space: pre;
}
blockquote { background: #fafcff; border-left: 4px solid #c7d7f5; margin: 16px 0; padding: 10px 12px; }
.section-meta { color: #4a4a4a; font-style: italic; margin-top: 4px; }
</style>

<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p>
<hr />

<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li>
    <li><a href="#descripcion-paso">1. Analizador léxico</a></li>
    <li><a href="#def-token">2. ¿Qué es un token?</a></li>
    <li><a href="#tokens-objetivo">3. Tokens que vamos a generar</a></li>
    <li><a href="#diagrama-lexer">4. Diagrama de bloques del analizador léxico</a></li>
    <li><a href="#lexer-pseudocodigo">5. Lexer en seudo‑código (adaptado a la nueva estructura)</a></li>
    <li><a href="#api-tokens">6. Estructura de datos: arreglo único y registro empaquetado</a></li>
    <li><a href="#esqueleto-ql-vb">7. Esqueleto QL / VB.NET</a></li>
  </ul>
</div>

<hr />

<!--============== CONTENIDO PRINCIPAL ==============-->

<h2 id="intro">Introducción</h2>

<p>
  En una entrada previa presentamos la gramática formal del <b>ZX BASIC del Spectrum 48K</b>.
  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
  <b>tokens</b>. Esta fase es el <b>analizador léxico</b> o <i>lexer</i>.
</p>

<p>
  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.&nbsp;</p><p>Usaremos la estuctura de datos definida en la entrada anterior para lamcenar los Tokens de la línea y pasarlos a la siguiente fase.&nbsp;</p>

<hr />

<h2 id="descripcion-paso">1. Analizador léxico</h2>
<p class="section-meta">Definiremos primero qué resuelve y qué limitaciones tiene. La misión del lexer es sencilla de expresar y delicada de implementar:</p>

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

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

<hr />

<h2 id="def-token">2. ¿Qué es un token?</h2>

<p>
  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:
</p>

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

<p>El parser solo opera sobre tokens, nunca sobre caracteres.</p>

<hr />

<h2 id="tokens-objetivo">3. Tokens que vamos a generar</h2>

<p class="section-meta">Derivados directamente de la gramática ZX BASIC formal.</p>

<h3>3.1 Identificadores y variables</h3>
<ul>
  <li><b>Identifier</b> — A, B2, COUNTER</li>
  <li><b>StringVar</b> — A$, NAME$</li>
</ul>

<h3>3.2 Literales</h3>
<ul>
  <li><b>IntegerLiteral</b> — enteros</li>
  <li><b>NumberLiteral</b> — entero, decimal o exponencial</li>
  <li><b>StringLiteral</b> — texto entre comillas</li>
</ul>

<h3>3.3 LineNumber</h3>
<ul>
  <li>Solo válido en columna 1</li>
  <li>1 a 4 dígitos, sin 0 inicial</li>
</ul>

<h3>3.4 Operadores</h3>
<ul>
  <li>+, -, *, /, ^</li>
  <li>=, &lt;&gt;, &lt;, &gt;, &lt;=, &gt;=</li>
  <li>AND, OR, NOT</li>
</ul>

<h3>3.5 Separadores</h3>
<ul>
  <li>( ) , ; :</li>
</ul>

<h3>3.6 Palabras clave</h3>
<ul>
  <li>LET, PRINT, INPUT, IF, THEN, FOR, …</li>
  <li>PLOT, DRAW, CIRCLE, POINT, …</li>
  <li>READ, DATA, RESTORE, DIM, CLS…</li>
  <li>REM</li>
</ul>

<h3>3.7 Comentarios</h3>
<ul>
  <li><b>Comment</b> — tras REM hasta fin de línea</li>
</ul>

<h3>3.8 Control</h3>
<ul>
  <li><b>EOL</b> — fin de línea</li>
  <li><b>EOFToken</b> — fin de fichero (si se procesa completo; no usado en QL línea a línea)</li>
</ul>

<hr />

<h2 id="diagrama-lexer">4. Diagrama de bloques del analizador léxico</h2>
<p class="section-meta">Flujo: leer línea → normalizar → escanear → clasificar → emitir tokens.</p>

<div style="text-align: center;">
  <img alt="Diagrama de flujo del generador de Tokens" src="https://blogger.googleusercontent.com/img/a/AVvXsEhH2S6D3TnIQn4fY57vrdB9ll4L4Gfef8BRYOaGxjGKHoI3LRW7YNZi2V9jCrL_YnhXPJ4bFKaHumYB8sNV0hkcuf_4CTS5B1GUmu5dWnqnnaGfB75AMKUTRukJM3ZoCO3RTDio7XLwO1s-WuzHiMOM8-1T5u8ZfxHVxLFMb1X4M-A5WFvxvuVq6da2-sk=w640-h427" width="640" />
  <div style="color: #444444; font-size: 0.9em; text-align: center;">Diagrama de flujo del generador de tokens</div>
</div>

<h3>4.3 Notas de implementación</h3>
<ul>
  <li>Normalizar CRLF/CR a <code>\n</code>.</li>
  <li>Keywords: <i>case‑insensitive</i>, se normalizan a MAYÚSCULAS.</li>
  <li><code>LineNumber</code> solo en columna 1.</li>
  <li>REM: dos variantes — <code>REM</code> vacío o <code>REM␠texto</code>.</li>
  <li>Prioridad de operadores dobles (&lt;=, &lt;&gt;, &gt;=).</li>
  <li>Diagnósticos detallados y recuperación local.</li>
</ul>

<hr />

<h2 id="lexer-pseudocodigo">5. Lexer en seudo‑código</h2>

<p class="section-meta">
El lexer genera&nbsp;la estructura de Tokens dentro de&nbsp;<code>TokenData$()</code> (un único arreglo de cadena) usando <code>Token.Emit(tipo, lexema$, línea, columna)</code>. Al final de cada línea emite <code>EOL</code>.
</p>

<pre><code>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 &gt; 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) &lt;&gt; "\""
                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 &lt; L ENTONCES
            par$ ← MID$(linea$, i, 2)
            SI par$ = "&lt;=" ENTONCES Token.Emit(T_LE, par$, numLinea, i) : i ← i + 2 : CONTINUAR MIENTRAS FIN SI
            SI par$ = "&gt;=" ENTONCES Token.Emit(T_GE, par$, numLinea, i) : i ← i + 2 : CONTINUAR MIENTRAS FIN SI
            SI par$ = "&lt;&gt;" 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 "&lt;" : Token.Emit(T_LT,     "&lt;", numLinea, i)
            CASO "&gt;" : Token.Emit(T_GT,     "&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
</code></pre>

<hr />

<h2 id="api-tokens">6. Estructura de datos: arreglo único y registro empaquetado</h2>

<p class="section-meta">
Los tokens se almacenan en <b>un único arreglo de cadenas</b> (<code>TokenData$()</code>), cada elemento contiene un <b>registro empaquetado binario</b>:
</p>

<blockquote>
<code>CHR$(tipo) &amp; MKI$(linea) &amp; MKI$(columna) &amp; CHR$(LEN(lexema$)) &amp; lexema$</code> (big‑endian)
</blockquote>

<pre><code>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 &gt;= TokenMax ENTONCES Token.Ampliar(TokenMax + 50)
    TokenCount ← TokenCount + 1
    TokenData$(TokenCount) ← CHR$(tipo) &amp; MKI$(linea) &amp; MKI$(columna) &amp; CHR$(LEN(lexema$)) &amp; 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
</code></pre>

<hr />

<h2 id="esqueleto-ql-vb">7. El programa generado en Visual Basic .NET</h2>

<p class="section-meta">Como ya he dicho (lo repito aquí por si entras directamente a esta entrada), el prototipo se desarrolla en VB.NET por velocidad y facilidad de depuración, y luego se portará a SuperBasic, manteniendo el mismo algoritmo y estructura de datos.Por ejemplo en QL podemos esribir</p>

<pre><code>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
</code></pre>

<p>
El proyecto del transpilador (hasta donde esté desarrollado) está en GitHub: <a href="https://github.com/javu61/ZX2SB" target="_blank">ZX2SB</a>.
</p>

<h3>Notas rápidas</h3>
<ul>
  <li><b>EOL normalizado</b> a <code>\n</code>.</li>
  <li><b>Keywords</b> en mayúsculas.</li>
  <li><b>LineNumber</b> solo en columna 1.</li>
  <li><b>REM</b>: variantes válidas documentadas.</li>
  <li><b>Números</b>: entero, fracción y exponente.</li>
  <li><b>Verbose</b>: opción para depurar mostrando los tokens en pantalla</li>
</ul>]]></description>
    </item>
    
    <item>
      <title>ZX2SB. Cambios en el planteamiento</title>
      <link>https://old8bits.blogspot.com/2026/03/zx2sb-cambios-en-el-planteamiento.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zx2sb-cambios-en-el-planteamiento.html</guid>
      <pubDate>Mon, 20 Apr 2026 11:50:28 -0000</pubDate>
      <description><![CDATA[<!--=========================================================
     CABECERA SEO — old8bits.blogspot.com
=========================================================-->
<title>Transpilador ZX BASIC a SuperBasic</title>

<meta content="Desarrollo de un transpilador dual ZX BASIC → SuperBasic. Para amantes del Sinclair QL o si quieres conocer sobre compiladores,con una versión moderna en VB.NET y una compatible con el hardware real." name="description"></meta>

<!--Open Graph (Facebook, LinkedIn, Discord)-->
<meta content="article" property="og:type"></meta>
<meta content="Transpilador ZX BASIC a SuperBasic" property="og:title"></meta>
<meta content="Diseño de un transpilador con doble motor: potencia moderna y fidelidad al Sinclair QL original." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com" property="og:url"></meta>
<meta content="Old8Bits" property="og:site_name"></meta>
<!--URL de la imagen del 40 aniversario-->
<meta content="https://sinclairqles.files.wordpress.com" property="og:image"></meta>

<!--Twitter Card-->
<meta content="summary_large_image" name="twitter:card"></meta>
<meta content="Transpilador ZX BASIC: Moderno vs Sinclair QL" name="twitter:title"></meta>
<meta content="Estructura y arquitectura de un transpilador dual para fans del Sinclair QL." name="twitter:description"></meta>
<meta content="https://sinclairqles.files.wordpress.com" name="twitter:image"></meta>
<meta content="@old8bits" name="twitter:site"></meta>

<!--Datos Estructurados para Google-->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Transpilador ZX BASIC a SuperBasic: Doble versión Moderno y QL",
  "image": "https://sinclairqles.files.wordpress.com",
  "author": {
    "@type": "Person",
    "name": "Old8Bits"
  },
  "keywords": "Sinclair QL, ZX BASIC, SuperBasic, Transpilador, VB.NET, Programación Retro"
}
</script>
<!--=========================================================
     ESTILOS DEL ÍNDICE (TOC)
=========================================================-->
<style>
.toc-container {
  background: #eef4ff;
  border: 1px solid #c7d7f5;
  padding: 16px 18px;
  margin: 18px 0 28px 0;
  border-radius: 6px;
  font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; list-style: disc; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }
</style>

<p></p>
<p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p>
<p style="text-align: center;"><span style="color: red;">20/03/2026 Cambios en el esquema de directorios en rojo</span></p><hr />

<!--=========================================================
     TABLA DE CONTENIDOS
=========================================================-->
<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li><li><a href="#dosversiones">Dos versiones en paralelo</a></li><li><a href="#motivos">¿Por qué dos versiones?</a></li>
    <li><a href="#lanzador">Pantalla lanzadora</a></li>
    <li><a href="#estructura">Arquitectura del proyecto</a></li>
    <li><a href="#conclusion">Conclusión</a></li>
  </ul>
</div>

<hr />

<!--=========================================================
     CONTENIDO PRINCIPAL
=========================================================-->

<h1 id="intro">Transpilador ZX BASIC → SuperBasic<br />  
Dos versiones en paralelo: Moderno y QL</h1>

<p>
En las últimas entradas hemos avanzado en el diseño interno del transpilador ZX BASIC → SuperBasic, definiendo estructuras de datos, el sistema de tokens y un Lexer optimizado para ejecutarse en un <b>Sinclair QL real</b>.  
Durante ese proceso me ha surgido una idea interesante para quienes quieran usar este proyecto no solo como herramienta, sino también para <b>aprender sobre compiladores</b>.
</p>

<h2 id="dosversiones">Vamos a generar dos versiones del transpilador en paralelo</h2>

<p>El objetivo es disponer de <b>dos rutas de ejecución simultáneas, </b>una preparada para migrar a SuperBASIC, y la otra usando elementos actuales.&nbsp;Ambas conviven dentro del mismo proyecto.  
La mayoría de procesos son comunes, por lo que es sencillo ver cómo pasar de una a otra.  
Además incluyo una pantalla donde puedes seleccionar cuál quieres lanzar.</p><p></p>

<h3 id="moderna">1) Versión Moderna</h3>

<p>Diseñada para:</p>

<ul>
  <li>Usar estructuras avanzadas de .NET (List, Dictionary, etc.).</li>
  <li>Desarrollo más rápido, depuración más cómoda y diagnósticos completos.</li>
  <li>Actuar como referencia clara del algoritmo general.</li>
</ul>

<h3 id="qlprep">2) Versión compatible con SuperBasic</h3>

<p>Implementa las estructuras reales que usará la versión final del QL:</p>

<ul>
  <li><b>TokenBuffer</b> como arreglo dinámico de registros empaquetados.</li>
  <li>Formato <b>big‑endian</b> de 16 bits, igual que el Motorola 68008.</li>
  <li>Procesos simples y lineales, estilo SuperBasic.</li>
</ul>

<p>
Esta versión sirve como puente entre .NET moderno y la implementación final en un QL real.
</p>

<h2 id="motivos">¿Por qué dos versiones?</h2>

<h3>✔ Didáctica</h3>
<p>Muestra cómo implementar un compilador moderno y cómo trasladarlo a una plataforma retro con limitaciones reales.</p>

<h3>✔ Validación de algoritmos</h3>
<p>Al ejecutar ambos motores en paralelo es fácil encontrar divergencias antes de portar código al QL real.</p>

<h3>✔ Documentación y mantenimiento</h3>
<p>
Permite explicar y probar Lexer, Parser, Semántico y Emitter tanto desde un enfoque moderno como desde un enfoque retro.
</p>

<h2 id="lanzador">Pantalla lanzadora</h2>

<p>Incluyo una interfaz WinForms que permite seleccionar:</p>

<ul>
  <li>Modo: <b>Moderno</b> o <b>QL‑Prep</b></li>
  <li><b>Ruta y nombre</b> del fichero BASIC</li>
  <li>Modo <b>Verbose</b></li>
</ul>

<p>
El lanzador invoca automáticamente el director correspondiente con las opciones necesarias.
</p>

<h2 id="estructura">Arquitectura del proyecto</h2>

<p>
El proyecto utiliza <b>.NET Framework 4.8</b> para asegurar compatibilidad con Windows 7/8/10/11 y aprovechar WinForms clásico.  
Para quienes pidan Windows XP, 95 o 3.11… lo siento, ¡aún no he retrocedido tanto!  
(Aunque con lo voluble que soy, cualquiera sabe si un día aparece una versión en Turbo‑C o Turbo Pascal 😄).&nbsp;</p><p><span style="color: red;">He tenido que ajustar otra vez la estructura de directorios del proyecto, ya que no se generaban las cosas en su lugar correcto, he dividido el proyecto en tres, lanzador, moderno y QL, cada uno es un proyecto independiente que genera su propio EXE, así es mas sencillo manejarlo de manera independiente.&nbsp;</span></p><p><span style="color: red;">Estructura general del proyecto:</span></p><pre><span style="color: red;">ZX2SB<br />│<br />├── ZX2SB.sln<br />├── README.md<br />│<br />├── Lanzador<br />│   ├── Lanzador.vbproj<br />│   └── src<br />│       ├── frmLanzador.vb<br />│       ├── frmLanzador.Designer.vb<br />│       ├── frmLanzador.resx<br />│       └── ... otros<br />│<br />├── Moderno<br />│   ├── Moderno.vbproj<br />│   └── src<br />│       ├── 01_Director.vb<br />        └── ... otros<br />└── QL<br />    ├── QL.vbproj<br />    └── src<br />        ├── 01_Director.vb<br />        └── ... otros</span></pre>

<h2 id="conclusion">Conclusión</h2>

<p>
A partir de ahora el desarrollo continúa en dos líneas paralelas:
</p>

<ul>
  <li><b>Moderno</b>: claridad, pruebas y comodidad.</li>
  <li><b>QL‑Prep</b>: compatibilidad y preparación para SuperBasic.</li>
</ul>

<p>
Ambas serán documentadas paso a paso en el blog.  
Por ahora ya está la estructura base en el repositorio GIT del proyecto, y los directores están preparados para llamar a los procesos (que de momento solo muestran un mensaje en pantalla).
</p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: El Analizador semántico</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-el-analizador-semantico.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-el-analizador-semantico.html</guid>
      <pubDate>Fri, 17 Apr 2026 08:19:40 -0000</pubDate>
      <description><![CDATA[<h2>Qué es un Analizador Semántico y por qué es una pieza clave en el transpilador</h2>

<p>
En un transpilador o un compilador clásico, el <b>analizador semántico</b> es la fase
encargada de comprobar que un programa, además de estar bien escrito desde el punto
de vista sintáctico, <i>tiene sentido</i>.
</p>

<p>
En el proyecto <b>ZX2SB</b>, cuyo objetivo es convertir código ZX BASIC a otro lenguaje, en nuestro caso SuperBASIC del QL, el analizador semántico juega
un papel fundamental: es el encargado de interpretar el significado real del programa.
</p>

<hr />

<h3>Las fases de un transpilador</h3>

<p>
Antes de entrar en detalle, conviene situar el analizador semántico dentro del proceso
completo:
</p>

<pre>Código fuente ZX BASIC
        |
        v
  Analizador léxico (Lexer)
        |
        v
 Analizador sintáctico (Parser)
        |
        v
 Analizador semántico (Semantic)
        |
        v
 Generador de código
        |
        v
 Código destino (SuperBASIC)
</pre>

<p>
Cada fase tiene una responsabilidad clara y bien delimitada.
</p>

<ul>
<li><b>Lexer</b>: convierte texto en tokens.</li>
<li><b>Parser</b>: verifica la estructura gramatical.</li>
<li><b>Semantic</b>: verifica el significado del programa.</li>
<li><b>Generator</b>: produce el código destino.</li>
</ul>

<hr />

<h3>Qué hace exactamente el analizador semántico</h3>

<p>
El analizador semántico responde a preguntas como:
</p>

<ul>
<li>¿Se usa una variable antes de asignarle un valor?</li>
<li>¿Se asigna una cadena a una variable numérica?</li>
<li>¿Un <code>NEXT</code> corresponde a su <code>FOR</code>?</li>
<li>¿Hay un <code>RETURN</code> sin <code>GOSUB</code> previo?</li>
<li>¿Las instrucciones <code>READ</code> tienen datos <code>DATA</code> suficientes?</li>
</ul>

<p>
En ZX BASIC muchas de estas situaciones están permitidas por el intérprete original,
pero al convertir a otros lenguajes conviene detectarlas y, al menos, avisar.
</p>

<hr />

<h3>Diseño del analizador semántico en ZX2SB</h3>

<p>
El analizador semántico de ZX2SB trabaja en <b>dos pasadas</b> sobre un formato intermedio
(IR):
</p>

<ol>
<li><b>Primera pasada</b>: recolección de información.</li>
<li><b>Segunda pasada</b>: análisis semántico y generación del IR final.</li>
</ol>

<h4>Primera pasada: recolección</h4>

<p>
En esta fase no se genera código. Se recopila información global:
</p>

<ul>
<li>Variables existentes y su tipo (numéricas o de cadena).</li>
<li>Uso de instrucciones especiales (PRINT, READ, DATA, AT, etc.).</li>
<li>Estructura del programa (FOR/NEXT, GOSUB/RETURN).</li>
<li>Mapa de líneas para referencias posteriores.</li>
</ul>

<p>
Esta información se guarda en un <i>contexto semántico</i> que se utiliza en la segunda pasada.
</p>

<h4>Segunda pasada: análisis y emisión</h4>

<p>
En la segunda pasada se analiza cada sentencia individualmente:
</p>

<ul>
<li>Se validan tipos.</li>
<li>Se marcan variables como usadas o asignadas.</li>
<li>Se detectan errores y avisos.</li>
<li>Se emite el IR normalizado que usará el generador.</li>
</ul>

<hr />

<h3>Errores y warnings: emisión inmediata</h3>

<p>
Inicialmente, el analizador semántico acumulaba errores y avisos en listas internas.
Sin embargo, en ZX2SB se ha optado por un diseño más simple y coherente:
</p>

<ul>
<li>Los errores y warnings se <b>emiten en el momento en que se detectan</b>.</li>
<li>La decisión de continuar o abortar se basa en las opciones del usuario.</li>
<li>Se eliminan listas auxiliares innecesarias.</li>
</ul>

<p>
Esto unifica el comportamiento con el Lexer, el Parser y el Generator.
</p>

<hr />

<h3>Diagrama del flujo del analizador semántico</h3>

<pre>+-----------------------------+
| Inicializar contexto        |
+-------------+---------------+
              |
              v
+-----------------------------+
| Primera pasada              |
| - Variables                 |
| - DATA / READ               |
| - Estructura                |
+-------------+---------------+
              |
              v
+-----------------------------+
| Segunda pasada              |
| - Analizar sentencias       |
| - Emitir errores/warnings   |
| - Generar IR normalizado    |
+-------------+---------------+
              |
              v
+-----------------------------+
| Resultado semántico         |
+-----------------------------+
</pre>

<hr />

<h3>Ejemplo de seudocódigo</h3>

<p>
El siguiente seudocódigo resume el funcionamiento básico:
</p>

<pre>function EjecutarSemantico():
    inicializar_contexto()

    primera_pasada()
    if error_fatal:
        salir

    segunda_pasada()
    emitir_warnings_variables()
</pre>

<p>
Y el análisis de una sentencia concreta:
</p>

<pre>function AnalizarLET(sentencia):
    comprobar_formato()
    comprobar_variable()
    comprobar_tipos()
    marcar_asignación()
    emitir_IR()
</pre>

<hr />

<h3>Por qué es importante esta fase</h3>

<p>
El analizador semántico es el lugar ideal para:
</p>

<ul>
<li>Detectar errores lógicos tempranamente.</li>
<li>Dar avisos útiles sin romper compatibilidad con ZX BASIC.</li>
<li>Preparar el código para distintos lenguajes destino.</li>
<li>Mantener el generador lo más simple posible.</li>
</ul>

<p>
Gracias a esta fase, ZX2SB puede crecer en el futuro hacia nuevos backends
(solo cambiando el generador) manteniendo una base sólida.
</p>

<hr />

<h3>Conclusión</h3>

<p>
El analizador semántico es mucho más que una comprobación adicional: es el
<b>puente entre la sintaxis y el significado</b>.
</p>

<p>
En ZX2SB se ha diseñado de forma clara, estructurada y cercana al espíritu de
los lenguajes clásicos, facilitando tanto el mantenimiento como la extensión
del proyecto.
</p>

<p>
En próximas entradas profundizaremos en el generador de código y en cómo
las decisiones semánticas influyen directamente en el resultado final.
</p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB: El Parser o Analizador Sintáctico</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-el-parser-o-analizador-sintactico.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-el-parser-o-analizador-sintactico.html</guid>
      <pubDate>Thu, 16 Apr 2026 19:04:04 -0000</pubDate>
      <description><![CDATA[<!--SEO-->
<p></p><p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p><hr />

<h1>El Analizador Sintáctico (Parser) en un Transpilador ZX BASIC</h1>
<p><i>Cómo funciona el Parser en ZX2SB y por qué es una pieza clave en la conversión de ZX BASIC</i></p>
<!--Tabla de contenidos-->
<h2>Índice</h2>
<ul>
  <li><a href="#parser-que-es">¿Qué es un Analizador Sintáctico?</a></li>
  <li><a href="#flujo">El flujo general del transpilador</a></li>
  <li><a href="#parser-funciones">Qué hace exactamente un Parser</a></li>
  <li><a href="#zx-parser">ZX BASIC y la necesidad de un Parser tolerante</a></li>
  <li><a href="#diseno">Diseño del Parser en ZX2SB</a></li>
  <li><a href="#diagrama">Diagrama del proceso del Parser</a></li>
  <li><a href="#errores">Gestión de errores y warnings</a></li>
  <li><a href="#seudocodigo">Ejemplo de seudocódigo</a></li>
  <li><a href="#semantic">Relación con el Analizador Semántico</a></li>
  <li><a href="#conclusion">Conclusión</a></li>
</ul>

<hr />

<h2 id="parser-que-es">¿Qué es el Analizador Sintáctico (Parser)?</h2>

<p>
En un transpilador o compilador, el <b>analizador sintáctico</b>, normalmente llamado
<b>Parser</b>, es la fase encargada de comprobar que un programa fuente
<b>está correctamente estructurado</b> según la gramática del lenguaje.
</p>

<p>
En el proyecto <b>ZX2SB</b>, el Parser se sitúa entre el analizador léxico y el
analizador semántico, actuando como un filtro estructural antes de interpretar
el significado real del programa.
</p>

<hr />

<h2 id="flujo">El flujo general del transpilador</h2>
El proceso de traducción o compilación sigue los pasos que ya hemos visto anteriormente, el parser sería el segundo paso del proceso completo:
<br />
<pre style="text-align: center;">&nbsp;</pre><pre style="text-align: center;">Código fuente ZX BASIC
|
v
  Analizador léxico (Lexer)
|
v
 <b>Analizador sintáctico (Parser)</b>
|
v
 Analizador semántico
|
v
 Generador de código
</pre>

<p>
Cada fase tiene una responsabilidad clara y no solapa funciones con las demás.
</p>

<ul>
<li><b>Lexer</b>: reconoce palabras, números, cadenas y símbolos.</li>
<li><b>Parser</b>: comprueba la estructura de las sentencias.</li>
<li><b>Semantic</b>: valida el significado y los tipos.</li>
<li><b>Generator</b>: produce el código destino.</li>
</ul>

<hr />

<h2 id="parser-funciones">Qué hace exactamente un Parser</h2>

<p>El analizador sintáctico debe responder a preguntas como estas (pero no todas se aplican al BASIC como veremos más adelante):</p>

<ul>
<li>¿La sentencia <b>IF</b> tiene un <b>THEN</b>?</li>
<li>¿Un <b>FOR</b> tiene su correspondiente <b>NEXT</b>?</li>
<li>¿La forma general de la instrucción es válida?</li>
<li>¿Los separadores y palabras clave están bien posicionados?</li>
</ul>

<p>Es importante destacar lo que el Parser <b>NO hace</b>:</p>

<ul>
<li>No comprueba tipos de datos (eso lo hace el analizador semántico).</li>
<li>No valida el uso de variables ni su significado.</li>
<li>No decide si una operación es lógica o correcta.</li>
<li>No genera código final.</li>
</ul>

<p>Su misión es exclusivamente <b>estructural</b>.</p>

<hr />

<h2 id="zx-parser">ZX BASIC y la necesidad de un Parser tolerante</h2>

<p>
ZX BASIC es un lenguaje muy permisivo. Muchas construcciones válidas
para el intérprete original pueden resultar ambiguas al convertirlas
a otros lenguajes.
</p>

<p>Por ese motivo, el Parser de ZX2SB está diseñado para:</p>

<ul>
<li>Aceptar la sintaxis original del ZX Spectrum.</li>
<li>Detectar errores estructurales claros.</li>
<li>Emitir <b>warnings</b> ante estructuras dudosas.</li>
<li>No ser excesivamente restrictivo.</li>
</ul>

<p>
Ejemplo de programa sintácticamente válido pero lógicamente erróneo:
</p>

<pre><code>
10 FOR i=1 TO 10
20   FOR j=1 TO 5
30     PRINT i,j
40   NEXT i
50 NEXT j
</code></pre>


<pre><code>
10 FOR i=1 TO 10
20   FOR j=1 TO 5 : PRINT i,j
30 NEXT i
</code></pre>

<p>
Estos programas no genera error en BASIC clásico, pero su comportamiento
no es el esperado y puede variar según el intérprete.
</p>

<hr />

<h2 id="diseno">Diseño del Parser en ZX2SB</h2>

<p>
El Parser trabaja sobre los <b>tokens</b> generados por el Lexer
y produce un <b>Árbol de Sintaxis Abstracta (AST)</b>.
</p>

<p>
Dado que el objetivo final es portar el sistema a <b>SuperBASIC</b>,
donde no existen estructuras de datos complejas, el AST se almacena
como una estructura lineal intermedia (<i>IR</i>).
</p>

<h4>Entrada</h4>
<ul>
<li>Secuencia de tokens.</li>
<li>Números de línea originales.</li>
<li>Contexto mínimo.</li>
</ul>

<h4>Salida</h4>
<ul>
<li>Sentencias normalizadas.</li>
<li>Estructura explícita del programa.</li>
<li>Errores y avisos.</li>
</ul>

<hr />

<h2 id="diagrama">Diagrama del proceso del Parser</h2>

<pre>+------------------------+
| Tokens del Lexer       |
+-----------+------------+
            |
            v
+------------------------+
| Reconocer sentencias   |
| (IF, LET, FOR, PRINT)  |
+-----------+------------+
            |
            v
+------------------------+
| Verificar estructura   |
+-----------+------------+
            |
            v
+------------------------+
| Emitir IR estructurado |
+------------------------+
</pre>

<hr />

<h2 id="errores">Gestión de errores y warnings</h2>

<ul>
<li><b>Errores</b>: la estructura es inválida.</li>
<li><b>Warnings</b>: la estructura es válida pero sospechosa.</li>
</ul>

<p>
La decisión de continuar o abortar depende de las opciones del usuario que se definan, no del Parser.En nuestro sistema, el usuario puede elegir entre parar y preguntar si desea continuar, o bien no parar el proceso y seguir hasta el final, los errores se verán en el fichero de log que se genera.</p>

<hr />

<h2 id="seudocodigo">Ejemplo de seudocódigo</h2>

<pre>function EjecutarParser(tokens):
    for cada línea:
        identificar sentencia
        validar estructura
        emitir error o aviso
    generar IR
</pre>

<hr />

<h2 id="semantic">Relación con el Analizador Semántico</h2>

<p>
El Parser prepara el terreno para el análisis semántico, permitiendo
que este se centre exclusivamente en el <b>significado lógico</b>.
</p>

<hr />

<h2 id="conclusion">Conclusión</h2>

<p>
El analizador sintáctico es la <b>columna vertebral estructural</b>
del transpilador. Sin interpretar significados, garantiza que
estos puedan analizarse correctamente más adelante.
</p>

<p>
En ZX2SB, el Parser respeta la filosofía del ZX BASIC original
y prepara el camino para futuras transformaciones a otros lenguajes.
</p>]]></description>
    </item>
    
    <item>
      <title>ZX2SB. Vuelta a empezar</title>
      <link>https://old8bits.blogspot.com/2026/04/zx2sb-vuelta-empezar.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/04/zx2sb-vuelta-empezar.html</guid>
      <pubDate>Thu, 16 Apr 2026 19:03:50 -0000</pubDate>
      <description><![CDATA[<p></p><p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a></p><hr />
<!--ÍNDICE-->
<div style="background: rgb(249, 249, 249); border: 1px solid rgb(204, 204, 204); padding: 10px;">
  <h3>Índice</h3>
  <ul>
    <li><a href="#introduccion">Introducción</a></li>
    <li><a href="#punto-partida">El punto de partida</a></li>
    <li><a href="#generador">El generador fue el primer aviso</a></li>
    <li><a href="#modulos">La ruptura conceptual: dividir en módulos</a></li>
    <li><a href="#tok">Primer paso: el fichero .TOK</a></li>
    <li><a href="#director">El papel del Director</a></li>
    <li><a href="#lexer">El resultado: un lexer cerrado</a></li>
    <li><a href="#conclusion">Conclusión</a></li>
    <li><a href="#postdata">Postdata</a></li>
  </ul>
</div>

<hr />
<h1>¿De transpilador a compilador?</h1>
<h2 id="introduccion">Evolución del proyecto y cambio de enfoque</h2>
<p>
Cuando empecé el proyecto de transpilación de <b>ZX BASIC a SuperBASIC (QL)</b>,
el objetivo parecía claro y razonable: traducir programas clásicos del Spectrum a un
SuperBASIC moderno, legible y ampliable, respetando en lo posible el espíritu original
pero sin arrastrar las limitaciones del intérprete de los años 80.
</p>

<p>
Al principio pensaba que sería relativamente sencillo: tokenizar el programa y
traducirlo a un lenguaje muy similar. Sin embargo, paso a paso el proyecto fue
complicándose. Empezaron a aparecer casos especiales, decisiones incómodas y pequeñas
incoherencias que, aunque no impedían avanzar, sí dificultaban hacerlo bien.
</p>

<p>
Sin embargo, como suele ocurrir en los proyectos que merecen la pena, el camino
no fue lineal. Lo que empezó como un “simple transpilador” acabó convirtiéndose en algo
conceptualmente más ambicioso: <b>una cadena de compilación modular</b>, con fases bien
definidas, contratos explícitos y decisiones arquitectónicas conscientes.
</p>

<hr />

<h2 id="punto-partida">El punto de partida: traducir, no reinterpretar</h2>

<p>Desde el principio hubo varias decisiones claras:</p>

<ul>
  <li>El código generado debía ser <b>SuperBASIC editable</b>, no un artefacto opaco.</li>
  <li>No se intentaría emular el ZX Spectrum al 100&nbsp;%:
    <ul>
      <li>nada de <code>PEEK</code> / <code>POKE</code></li>
      <li>nada de trucos de automodificación</li>
    </ul>
  </li>
  <li>
    Las funciones ZX no soportadas se resolverían mediante llamadas a
    <b>rutinas auxiliares</b> escritas en SuperBASIC.
  </li>
  <li>El resultado debía ser <b>extensible</b>, pensando en mejoras futuras.</li>
</ul>

<p>
El transpilador original funcionaba como un bloque monolítico:
leer una línea, analizarla, transformarla y generar código QL.
</p>

<p>
Funcionaba, pero conforme el proyecto crecía, también crecían las dudas.
</p>

<hr />

<h2 id="generador">El generador fue el primer aviso</h2>

<p>
El proyecto avanzaba, con cambios en el lexer y el parser por el camino,
pero fue el generador el que puso el primer escalón incómodo delante del diseño.
</p>

<p>
Para generar buen SuperBASIC había que responder a preguntas que un
“transpilador rápido” no suele plantearse:
</p>

<ul>
  <li>¿Cómo se numeran las líneas para facilitar la edición manual?</li>
  <li>¿Dónde debe ir la inicialización del entorno gráfico?</li>
  <li>¿Cómo se insertan subrutinas (<code>PROC</code>) sin romper el flujo?</li>
  <li>¿Qué partes del código son infraestructura y cuáles lógica del programa?</li>
</ul>

<blockquote>
  <p><b>
    Si el generador necesita un AST claro, el resto del sistema también
    debería pensarse como un compilador.
  </b></p>
</blockquote>

<p>
Reescribir y replantear no es perder tiempo: es ganar claridad,
mantenibilidad y, muchas veces, velocidad.
</p>

<hr />

<h2 id="modulos">La ruptura conceptual: dividir en módulos</h2>

<p>
El diseño monolítico, pensado para optimizar el QL, terminó siendo un error.
La decisión clave fue:
</p>

<blockquote>
  <p><b><i>
    Dividir el sistema en módulos independientes, comunicados solo por ficheros.
  </i></b></p>
</blockquote>

<ol>
  <li><b>Director</b>: orquesta el proceso, sin conocer el lenguaje.</li>
  <li><b>Lexer</b>: reconoce ZX BASIC y genera tokens.</li>
  <li><b>Parser</b>: construye el AST.</li>
  <li><b>Semántico</b>: valida y ajusta el AST.</li>
  <li><b>Generador</b>: produce SuperBASIC editable.</li>
</ol>

<hr />

<h2 id="tok">Primer paso: el fichero .TOK</h2>

<p>
El fichero <code>.tok</code> es la frontera definitiva entre fases:
</p>

<ul>
  <li>texto plano</li>
  <li>una línea por token</li>
  <li><code>LINE</code>, <code>EOL</code>, <code>EOF</code></li>
  <li>sin posiciones ni contexto oculto</li>
  <li><b>la única fuente de verdad para el parser</b></li>
</ul>

<pre><code>LINE 50
Keyword IF
Identifier C
OpMayorIgual &gt;=
Number 20
Keyword THEN
Keyword GOTO
Number 100
EOL</code></pre>

<p>
Si algo no está en el <code>.tok</code>, el parser no lo sabe.
</p>

<hr />

<h2 id="director">El papel del Director: menos es más</h2>

<blockquote>
  <p><b>
    El Director no debe saber si el lenguaje es ZX BASIC, C o Pascal.
  </b></p>
</blockquote>

<p>
El lexer reconoce y valida el número de línea.
El Director solo coordina.
</p>

<hr />

<h2 id="lexer">El resultado: un lexer cerrado</h2>

<ul>
  <li>Reconoce ZX BASIC correctamente</li>
  <li>Detecta errores léxicos reales</li>
  <li>No adelanta semántica</li>
  <li>Genera un <code>.tok</code> definitivo</li>
</ul>

<blockquote>
  <p><b>Esta fase está cerrada.</b></p>
</blockquote>

<hr />

<h2 id="conclusion">Conclusión</h2>

<p>
El proyecto ya no es solo un transpilador:
es una base sólida para un posible compilador real.
</p>

<p>
El lexer está cerrado, el contrato existe,
y el camino es ahora mucho más claro.
</p>

<hr />

<h2 id="postdata">Postdata</h2>

<p>
Este proyecto es complejo y formativo.
Revisar, dudar, reescribir y aprender
forma parte esencial del proceso.
</p>

<p>
La ayuda de herramientas como Copilot ha sido clave,
siempre revisando, cuestionando y aprendiendo.
</p>]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Paso 3.1. Lexer. Estructura de datos para manejo de Tokens</title>
      <link>https://old8bits.blogspot.com/2026/03/zb2sb-paso-3-lexer-estructura-de-datos.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zb2sb-paso-3-lexer-estructura-de-datos.html</guid>
      <pubDate>Sun, 15 Mar 2026 21:41:46 -0000</pubDate>
      <description><![CDATA[<!-- =========================================================
       CABECERA SEO OPTIMIZADA — old8bits.blogspot.com
       Keyword principal: Transpilador ZX BASIC Sinclair QL
     ========================================================= -->

<title>Transpilador ZX BASIC para Sinclair QL: Optimización del Lexer (Fase 1)</title>
<meta content="Guía técnica para optimizar el Lexer de un transpilador ZX BASIC en Sinclair QL. Uso de registros empaquetados y formato binario para ahorrar memoria." name="description"></meta>

<!-- Open Graph (Redes Sociales) -->
<meta content="article" property="og:type"></meta>
<meta content="Cómo desarrollar un Lexer, optimizado para Sinclair QL: Transpilador ZX BASIC" property="og:title"></meta>
<meta content="Implementación de un sistema de tokens con registros empaquetados en formato binario. Ideal para programadores de Sinclair QL y sistemas retro." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com" property="og:url"></meta>

<!-- Datos Estructurados (Schema.org) mejorados -->
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@type":"TechArticle",
  "headline":"Transpilador ZX BASIC para Sinclair QL — Lexer: Estructura de Tokens",
  "description":"Optimización del Lexer mediante registros empaquetados binarios para el procesador 68008 del Sinclair QL.",
  "image":["https://upload.wikimedia.org"],
  "author":{"@type":"Person","name":"javu61"},
  "keywords":["Sinclair QL","ZX BASIC","SuperBasic","Lexer","Programación Retro","Motorola 68008","Big-endian"],
  "articleSection":"Programación Retro"
}
</script>

<!--  =========================================================
      ====== ESTILOS (TOC + Código claro y sin “doble caja”)
      ========================================================= -->
<style>
.toc-container {
  background: #eef4ff; border: 1px solid #c7d7f5; padding: 16px 18px;
  margin: 18px 0 28px 0; border-radius: 6px; font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; list-style: disc; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }

blockquote { background:#fafcff; border-left:4px solid #c7d7f5; margin:16px 0; padding:10px 12px; }
.section-meta { color:#4a4a4a; font-style:italic; margin-top:4px; }

/* Código en bloque (tema claro, sin doble caja) */
body .post-body pre, .post-body pre, pre {
  background: #f6f8fa; color: #0b1220; border: 1px solid #e1e4e8; border-radius: 8px;
  padding: 14px 16px; margin: 14px 0 18px; overflow-x: auto;
  line-height: 1.45;
  font: 0.95rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  tab-size: 4; white-space: pre;
}
body .post-body pre code, .post-body pre code, pre code {
  display: contents; background: transparent !important; border: 0 !important; padding: 0 !important; color: inherit;
}
/* Código inline */
body .post-body code:not(pre code), .post-body code:not(pre code), code:not(pre code) {
  background: #f5f7fb; color: #111827; border: 1px solid #e5e7eb; border-radius: 4px;
  padding: 0.12em 0.33em; font: 0.92em ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; white-space: nowrap;
}
@media (max-width: 520px) { body .post-body pre, .post-body pre, pre { font-size: 0.9rem; } }
</style>

<p></p><p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor<br /></a></p><hr />


<!--  =========================================================
      ====== TABLA DE CONTENIDOS
      ========================================================= -->
<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li>
    <li><a href="#diseno">1. Diseño del registro de Token (formato binario)</a></li>
    <li><a href="#mki-cvi">2. Funciones MKI$ y CVI (seudocódigo compatible QL)</a></li>
    <li><a href="#api">3. API de gestión de Tokens</a></li>
    <li><a href="#encode">4. Empaquetado y acceso a los datos del registro</a></li>
    <li><a href="#alta">5. Alta de nuevos tokens en el arreglo</a></li>
    <li><a href="#prueba">6. Prueba: crear registros, recuperarlos y mostrarlos</a></li>
    <li><a href="#siguientes">7. Siguiente paso</a></li>
  </ul>
</div>

<hr />

<!--============== CONTENIDO PRINCIPAL ==============-->

<h2 id="intro">Introducción</h2>

<p>
  Para ejecutar el transpilador ZX BASIC → SuperBasic en un <b>Sinclair QL real</b> debemos optimizar al máximo
  la <b>memoria</b> y la <b>velocidad</b>. Por ello, el Lexer usará un <b>único arreglo de cadena</b>
  donde <b>cada elemento es un token en un registro empaquetado</b> (formato binario compacto), en lugar de varios arreglos
  paralelos. Esto reduce la fragmentación de memoria, simplifica las ampliaciones y acelera copias.
</p>
<p>
  El objetivo de esta entrada es implementar, en seudo‑código, <b>todas las funciones necesarias para manejar
  la estructura de Tokens</b> (crear, ampliar, resetear, insertar y leer) y añadir funciones de <b>prueba</b> 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.
</p>

<hr />

<h2 id="diseno">1. Diseño del registro de Token (formato binario)</h2>

<blockquote>
Formato de registro (<b>TokenRecord</b>):<br />
<code>CHR$(tipo) &amp; MKI$(linea) &amp; MKI$(columna) &amp; CHR$(LEN(lexema$)) &amp; lexema$</code>
</blockquote>

<ul>
  <li><b>tipo</b> → byte (0..255).</li>
  <li><b>línea</b> → entero sin signo de 16‑bit (2 bytes).</li>
  <li><b>columna</b> → entero sin signo de 16‑bit (2 bytes).</li>
  <li><b>len(lexema$)</b> → byte (0..255).</li>
  <li><b>lexema$</b> → cadena (longitud variable).</li>
</ul>

<p class="section-meta">
  Compacto, rápido y alineado con el manejo de cadenas del QL.
</p>

<hr />

<h2 id="mki-cvi">2. Funciones MKI$ y CVI (seudocódigo compatible QL)</h2>


<p><b>Aclaración sobre endianness:</b> 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):</p>
<ul>
  <li><b>Big‑endian</b> (primero el byte más significativo, <i>MSB</i>): el número se mantiene en el orden “natural” de sus bytes, es decir, en <b>binario puro</b> con el MSB antes que el LSB. Es el formato nativo del <b>68008</b> (y toda la familia 680x0), y por tanto el que usa el <b>QL</b>. También lo emplearon arquitecturas como <b>SPARC</b> (en muchas implementaciones), <b>PowerPC</b> (en configuraciones clásicas de servidores/Unix) y ciertos sistemas <b>IBM</b> de gran porte. Es el formato que usaremos en nuestros procesos.</li>
  <li><b>Little‑endian</b> (primero el byte menos significativo, <i>LSB</i>): el número se almacena con el orden de bytes invertido. Aunque pueda parecer menos intuitivo, favorece algunas <b>operaciones aritméticas con acarreo</b> al comenzar por el LSB y propagar el acarreo hacia los bytes más altos. Es el formato usado en los&nbsp;<b>PC con Intel</b>&nbsp;y también el predeterminado en la mayoría de sistemas <b>ARM</b> y <b>RISC‑V</b> actuales, y fue el usado en los&nbsp;<b>DEC PDP</b> .</li>
</ul>

<pre><code>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 &lt; 0 ENTONCES ERROR No se soportan negativos
    SI n &gt; 65535 ENTONCES ERROR Número demasiado grande

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

    s$ ← CHR$(hi) &amp; 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
</code></pre>

<hr />

<h2 id="api">3. API de gestión de Tokens</h2>

<pre><code>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
</code></pre>

<hr />

<h2 id="encode">4. Empaquetado y acceso a los datos del registro</h2>

<pre><code>REM =========================================
REM  Empaquetado / Acceso a campos del token
REM =========================================

FUNCIÓN Token.Encode(tipo, linea, columna, lexema$) → token$
    token$ ← CHR$(tipo) &amp; MKI$(linea) &amp; MKI$(columna) &amp; CHR$(LEN(lexema$)) &amp; 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
</code></pre>

<hr />

<h2 id="alta">5. Alta de nuevos tokens en el arreglo</h2>

<pre><code>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 &gt;= TokenMax ENTONCES Token.Ampliar(TokenMax + 50)

    TokenCount ← TokenCount + 1
    TokenData$(TokenCount) ← Token.Encode(tipo, linea, columna, lexema$)
FIN
</code></pre>

<p class="section-meta">
  Esta operación codifica el registro y lo guarda como una cadena en <code>TokenData$(n)</code>.
</p>

<hr />

<h2 id="prueba">6. Prueba: crear registros, recuperarlos y mostrarlos</h2>

<pre><code>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  -&gt;"
            Ver(tipo, lin, col, lenLex, lex$)
        SINO
            fallos ← fallos + 1
            PRINT i; ": ERR -&gt;  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
</code></pre>

<hr />

<h2 id="siguientes">7. Siguiente paso</h2>
<ul>
  <li>Integrar estos cambios en el <b>Lexer</b> real (Fase 2): sustituir escrituras paralelas por <code>Token.Emit</code> y usar <code>TokenData$()</code> en el Parser con <code>Token.Get*</code>.</li>
  <li>Publicar el módulo equivalente en <b>VB.NET</b> (Encode/Decode + pruebas), con <i>BitConverter</i> e inversión a big‑endian.</li>
</ul>

<p class="section-meta">
  En la siguiente entrega convierto este seudo‑código a <b>VB.NET</b> con pruebas unitarias.
</p>]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Paso Auxiliar 1. Estructuras de datos</title>
      <link>https://old8bits.blogspot.com/2026/03/zb2sb-paso-auxiliar-1-estructuras-de.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zb2sb-paso-auxiliar-1-estructuras-de.html</guid>
      <pubDate>Sat, 14 Mar 2026 07:05:35 -0000</pubDate>
      <description><![CDATA[<!--=========================================================
     CABECERA SEO — old8bits.blogspot.com
=========================================================-->

<title>Transpilador ZX BASIC — Estructuras de datos para el transpilador (y adaptación a SuperBasic)</title>
<meta content="Diseño de todas las estructuras de datos necesarias para implementar el transpilador ZX BASIC → SuperBasic, incluyendo listas de tokens, AST, tabla de líneas, diagnósticos y salida. Seudo-código adaptado al Sinclair QL y su SuperBasic." name="description"></meta>

<!--Open Graph-->
<meta content="article" property="og:type"></meta>
<meta content="Transpilador ZX BASIC — Estructuras de datos para el transpilador" property="og:title"></meta>
<meta content="Descripción completa de las estructuras de datos del transpilador y su equivalente en SuperBasic del QL." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com/2026/03/transpilador-zx-basic-estructuras-datos.html" property="og:url"></meta>
<meta content="old8bits" property="og:site_name"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" property="og:image"></meta>

<!--Twitter Cards-->
<meta content="summary_large_image" name="twitter:card"></meta>
<meta content="Transpilador ZX BASIC — Estructuras de datos para el transpilador" name="twitter:title"></meta>
<meta content="Definición y seudo-código de todas las estructuras necesarias para portar el transpilador ZX BASIC → SuperBasic." name="twitter:description"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" name="twitter:image"></meta>
<meta content="@old8bits" name="twitter:site"></meta>

<!--Datos estructurados-->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Transpilador ZX BASIC — Estructuras de datos",
  "description": "Diseño de las estructuras de datos del transpilador ZX BASIC → SuperBasic, adaptadas al Sinclair QL.",
  "author": { "@type": "Person", "name": "javu61" },
  "publisher": { "@type": "Organization", "name": "old8bits" },
  "datePublished": "2026-03-15T10:00:00+01:00",
  "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg"
}
</script>

<!--=========================================================
====== ESTILOS (TOC + Código claro y sin “doble caja”)
=========================================================-->
<style>
/* TOC / tipografía base */
.toc-container {
  background: #eef4ff;
  border: 1px solid #c7d7f5;
  padding: 16px 18px;
  margin: 18px 0 28px 0;
  border-radius: 6px;
  font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; list-style: disc; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }

blockquote {
  background:#fafcff;
  border-left:4px solid #c7d7f5;
  margin:16px 0;
  padding:10px 12px;
}
.section-meta { color:#4a4a4a; font-style:italic; margin-top:4px; }

/* ===== Bloques de código (tema claro estilo GitHub) ===== */
body .post-body pre,
.post-body pre,
pre {
  background: #f6f8fa;
  color: #0b1220;
  border: 1px solid #e1e4e8;
  border-radius: 8px;
  padding: 14px 16px;
  margin: 14px 0 18px;
  overflow-x: auto;              /* scroll horizontal si hay líneas largas */
  line-height: 1.45;
  font: 0.95rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  tab-size: 4;
  white-space: pre;              /* respeta espacios y saltos */
}

/* Evita la “doble caja”: el <code> interno no añade fondo ni borde */
body .post-body pre code,
.post-body pre code,
pre code {
  display: contents;             /* “aplana” el <code> interior */
  background: transparent !important;
  border: 0 !important;
  padding: 0 !important;
  color: inherit;
}

/* ===== Código en línea ===== */
body .post-body code:not(pre code),
.post-body code:not(pre code),
code:not(pre code) {
  background: #f5f7fb;
  color: #111827;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 0.12em 0.33em;
  font: 0.92em ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  white-space: nowrap;
}

/* Móvil: bloque un poco más pequeño */
@media (max-width: 520px) {
  body .post-body pre,
  .post-body pre,
  pre { font-size: 0.9rem; }
}
</style>

<p></p><p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor<br /></a></p><hr />

<!--=========================================================
====== TABLA DE CONTENIDOS
=========================================================-->
<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li>
    <li><a href="#sec1">1. Por qué necesitamos estructuras propias</a></li>
    <li><a href="#sec2">2. Qué es un registro</a></li>
    <li><a href="#sec2-1">2.1 Campos dentro de un registro</a></li>
    <li><a href="#sec2-2">2.2 Cómo representaremos un registro</a></li>
    <li><a href="#sec2-3">2.3 Formatos de empaquetado para registros</a></li>
    <li><a href="#sec3">3. Almacenamiento de cadenas en el QL real</a></li>
    <li><a href="#sec3-1">3.1 ¿Qué hay realmente en un arreglo de cadenas?</a></li>
    <li><a href="#sec4">4. Comparativa de modelos de estructuras en SuperBasic</a></li>
    <li><a href="#sec5">5. Estructuras de datos del transpilador</a></li>
    <li><a href="#sec6">6. Descripción detallada de cada estructura</a></li>
    <li><a href="#sec7">7. Seudo‑código de las estructuras</a></li>
    <li><a href="#sec8">8. Siguientes pasos</a></li>
  </ul>
</div>



<!--============== CONTENIDO PRINCIPAL ==============-->

<h2 id="intro">Introducción</h2>

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

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

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

<p>
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.
</p>

<p>
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
<b>arreglo unidimensional</b> (o simplemente <b>arreglo</b>) para lo que habitualmente se denomina array, de <b>arreglo multidimensional</b>
(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&nbsp;<b>tensor</b>).
</p>

<hr />

<h2 id="sec1">1. Por qué necesitamos estructuras propias</h2>

<p>SuperBasic no dispone de:</p>

<ul>
  <li>listas dinámicas,</li>
  <li>arreglos redimensionables sin pérdida,</li>
  <li>tipos compuestos o registros,</li>
  <li>pilas, colas o diccionarios,</li>
  <li>árboles.</li>
</ul>

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

<h2 id="sec2">2. Qué es un registro</h2>

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

<p>
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 <b>representamos usando varios arreglos paralelos</b> o <b>empleando un único arreglo
de cadenas que contiene todos los campos empaquetados</b>.
</p>

<h3 id="sec2-1">2.1 Campos dentro de un registro</h3>

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

<ul>
  <li><b>tipo</b> del token,</li>
  <li><b>lexema</b> (cadena original),</li>
  <li><b>línea</b> donde aparece,</li>
  <li><b>columna</b> dentro de esa línea,</li>
  <li><b>longitud</b> del lexema.</li>
</ul>

<p>
Todos esos campos juntos forman un solo <b>registro Token</b>.
</p>

<h3 id="sec2-2">2.2 Cómo representaremos un registro</h3>

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

<ul>
  <li><b>Mediante varios arreglos paralelos</b>:  
      cada campo del registro se guarda en un arreglo independiente, y el índice <code>n</code>
      identifica el mismo registro en todos ellos.</li>

  <li><b>Mediante un único arreglo de cadena con los datos&nbsp;empaquetados</b>:  
      cada elemento del arreglo contiene todos los campos del registro codificados en una sola
      cadena, usando separadores o marcas de longitud.</li>
</ul>

<p>
Ambos métodos expresan el mismo concepto: <b>un registro es un conjunto de campos que pertenecen juntos</b>,
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.).
</p>

<p>
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.
</p>

<h3 id="sec2-3">2.3 Formatos de empaquetado para registros</h3>

<p>
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.
</p>

<ul>
  <li><b>A)</b> Separadores entre campos (p. ej. <code>"3|A$|120|5|2"</code>): legible y sencillo, 
      adecuado para estructuras poco críticas en rendimiento.</li>

  <li><b>B)</b> Campos de tamaño fijo: rápido de extraer mediante <code>MID$</code>, aunque menos flexible
      y con espacio desaprovechado.</li>

  <li><b>C)</b> Longitud + campo (formato binario compacto): 
      <br /><code>CHR$(tipo) &amp; MKI$(linea) &amp; MKI$(col) &amp; CHR$(LEN(lex$)) &amp; lex$</code>
      <br />Es el método más rápido y más eficiente para estructuras que se consultarán miles de veces.</li>
</ul>

<p>
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.
</p>

<h2 id="sec3">3. Almacenamiento de cadenas en el QL real</h2>

<p>
El <b>SuperBasic del Sinclair QL</b> 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
<b>arreglo de cadenas</b> contiene un <b>descriptor</b> (puntero + longitud), y el texto vive en el
<b>heap dinámico del QDOS</b>.
</p>

<h3 id="sec3-1">3.1 ¿Qué hay realmente en un arreglo de cadenas?</h3>

<p>
Cuando declaramos un arreglo de cadenas como:
</p>

<pre><code>DIM A$(9)
A$(0) = "HOLA"
A$(1) = "QL"
A$(2) = ""
A$(3) = "TRANS"</code></pre>

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

<ul>
  <li>un <b>puntero</b> a la cadena real almacenada en el heap del QDOS,</li>
  <li>la <b>longitud</b> de esa cadena.</li>
</ul>

<p>
Por tanto, tras las asignaciones, la estructura interna del arreglo es:
</p>

<pre><code>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)  → ""</code></pre>

<p>
Obsérvese que <b>ninguna cadena se guarda dentro del arreglo</b>.  
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).
</p>

<hr />

<h2 id="sec4">4. Comparativa de modelos de estructuras en SuperBasic</h2>

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

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

<h3>4.2 Arreglo multidimensional</h3>
<ul>
  <li><b>Qué es:</b> <code>DIM Datos(N, M)</code> (numérico).</li>
  <li><b>Límite:</b> con cadenas no resuelve el problema; seguiría siendo necesario otro arreglo <code>$</code>.</li>
  <li><b>Uso:</b> no recomendado para nuestras estructuras con texto.</li>
</ul>

<h3>4.3 Un único arreglo de cadena con los datos empaquetados</h3>
<ul>
  <li><b>Qué es:</b> <code>Datos$()</code> donde cada elemento empaqueta campos (tipo, línea, col., lexema…).</li>
  <li><b>Ventajas:</b> se amplía y copia un solo arreglo; menos fragmentación.</li>
  <li><b>Inconvenientes:</b> hay que empaquetar/desempaquetar; ligera penalización de CPU.</li>
  <li><b>Uso:</b> muy adecuado para Tokens, Diagnósticos, Símbolos, Tabla de líneas y Salida.</li>
</ul>

<hr />

<h2 id="sec5">5. Estructuras de datos del transpilador</h2>

<p>Las estructuras necesarias son:</p>

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

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

<hr />
<h2 id="sec6">6. Descripción detallada de cada estructura</h2>

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

<hr />

<h3>6.1 Tokens (Lexer)</h3>

<h4>6.1.1 Representación</h4>
<p>
Un <b>único arreglo de cadenas empaquetadas</b> (formato binario compacto) por token:
</p>
<pre><code>TokenData$(n)
TokenCount
TokenMax</code></pre>

<h4>6.1.2 Registro: <i>TokenRecord</i></h4>
<pre><code>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$</code></pre>

<h4>6.1.3 Empaquetado</h4>
<pre><code>TokenData$(n) =
  CHR$(tipo) &amp;
  MKI$(linea) &amp;
  MKI$(columna) &amp;
  CHR$(LEN(lexema$)) &amp;
  lexema$</code></pre>

<hr />

<h3>6.2 AST por línea (Parser)</h3>

<h4>6.2.1 Representación</h4>
<p>
El AST (línea, sentencias y expresiones) es una estructura <b>jerárquica</b>. Para mantener los
enlaces entre nodos y recorrer el “árbol” con claridad y rapidez en QL, se usarán&nbsp;<b>varios arreglos paralelos</b>.
</p>

<h4>6.2.2 Nodo de Línea</h4>
<pre><code>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</code></pre>

<h4>6.2.3 Sentencias</h4>
<pre><code>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</code></pre>

<h4>6.2.4 Expresiones</h4>
<pre><code>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</code></pre>

<hr />

<h3>6.3 Tabla de Líneas (Semántico)</h3>

<h4>6.3.1 Representación</h4>
<p>
Un <b>único arreglo de cadenas</b> con <b>separadores</b> (legible y suficiente en rendimiento):
</p>
<pre><code>LineData$(n)   = numeroLinea$ &amp; "|" &amp; astIndex$
LineCount
LineMax</code></pre>

<h4>6.3.2 Registro: <i>LineRecord</i></h4>
<pre><code>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"</code></pre>

<hr />

<h3>6.4 Tabla de Símbolos</h3>

<h4>6.4.1 Representación</h4>
<p>
Un <b>único arreglo de cadenas</b> con <b>separadores</b>:
</p>
<pre><code>SymbolData$(n) = nombre$ &amp; "|" &amp; tipo$
SymbolCount
SymbolMax</code></pre>

<h4>6.4.2 Registro: <i>SymbolRecord</i></h4>
<pre><code>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"</code></pre>

<hr />

<h3>6.5 Diagnósticos</h3>

<h4>6.5.1 Representación</h4>
<p>
Un <b>único arreglo de cadenas</b> con <b>separadores</b>:
</p>
<pre><code>DiagData$(n) = mensaje$ &amp; "|" &amp; linea$ &amp; "|" &amp; columna$ &amp; "|" &amp; fase$
DiagCount
DiagMax</code></pre>

<h4>6.5.2 Registro: <i>DiagRecord</i></h4>
<pre><code>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"</code></pre>

<hr />

<h3>6.6 Buffer de salida</h3>

<h4>6.6.1 Representación</h4>
<p>
Un <b>único arreglo de cadenas</b> donde <b>cada elemento es la línea destino completa</b>:
</p>
<pre><code>Output$(n)    = lineaSuperBasic$
OutputCount
OutputMax</code></pre>

<h4>6.6.2 Registro: <i>OutputRecord</i></h4>
<pre><code>Campo        : LineaSB
Tipo de dato : cadena (longitud variable)
Descripción  : Línea completa ya generada en SuperBasic
Almacenado   : "100 PRINT \"HOLA\""</code></pre>

<h2 id="sec7">7. Seudo‑código de las estructuras</h2>

<p class="section-meta">
Convención: <b>var X</b> = parámetro por referencia (modifica el valor externo).
<b>X</b> sin <code>var</code> = parámetro por valor.
</p>

<h3>7.0 Funciones genéricas para usar arreglos como lista</h3>

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

<pre><code>// ======================================================================
//  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 &lt;= 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</code></pre>

<h3>7.X Firmas de funciones por estructura (sin implementación)</h3>

<pre><code>// 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$)</code></pre>

<h3>7.1 Tokens (implementacióncompleta en seudo‑código como ejemplo)</h3>

<pre><code>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 &gt;= 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</code></pre>

<hr />

<h2 id="sec8">8. Siguientes pasos</h2>

<p>
Ya tenemos la base necesaria para portar todas las fases del transpilador al QL:
</p>

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

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

<hr />]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Paso 2. Director del proyecto</title>
      <link>https://old8bits.blogspot.com/2026/03/zb2sb-paso-2-director-del-proyecto.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zb2sb-paso-2-director-del-proyecto.html</guid>
      <pubDate>Sat, 14 Mar 2026 07:04:27 -0000</pubDate>
      <description><![CDATA[<!--=========================================================
     CABECERA SEO — old8bits.blogspot.com
=========================================================-->

<title>Transpilador ZX BASIC — El Director del Proceso (Orquestador del Pipeline)</title>
<meta content="Diseño del Director del Proceso para el transpilador ZX BASIC → SuperBasic. Control centralizado del pipeline: lectura línea a línea, lexer por líneas, parser por líneas, transformaciones y generación de código." name="description"></meta>

<!--Open Graph-->
<meta content="article" property="og:type"></meta>
<meta content="Transpilador ZX BASIC — El Director del Proceso" property="og:title"></meta>
<meta content="Clase central que controla el flujo del transpilador: lexer por líneas, parser por líneas, transformaciones y generación de código SuperBasic, optimización y salida." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com/2026/03/transpilador-zx-basic-director-proceso.html" property="og:url"></meta>
<meta content="old8bits" property="og:site_name"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" property="og:image"></meta>

<!--Twitter Cards-->
<meta content="summary_large_image" name="twitter:card"></meta>
<meta content="Transpilador ZX BASIC — El Director del Proceso" name="twitter:title"></meta>
<meta content="Orquestación completa del transpilador ZX BASIC optimizada para QL." name="twitter:description"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" name="twitter:image"></meta>
<meta content="@old8bits" name="twitter:site"></meta>

<!--Datos estructurados (schema.org/TechArticle)-->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "mainEntityOfPage": { "@type": "WebPage", "@id": "https://old8bits.blogspot.com/2026/03/transpilador-zx-basic-director-proceso.html" },
  "headline": "Transpilador ZX BASIC — El Director del Proceso",
  "description": "Diseño del Director del Proceso (TranspilerDriver) para el transpilador ZX BASIC, optimizado para QL: lectura y procesamiento por líneas.",
  "image": [ "https://blogger.googleusercontent.com/IMAGEN_REEMPLAZAR.jpg" ],
  "author": { "@type": "Person", "name": "javu61" },
  "publisher": {
    "@type": "Organization",
    "name": "old8bits",
    "logo": {
      "@type": "ImageObject",
      "url": "https://blogger.googleusercontent.com/LOGO_REEMPLAZAR.png",
      "width": 180,
      "height": 180
    }
  },
  "datePublished": "2026-03-12T10:00:00+01:00",
  "dateModified":  "2026-03-12T10:00:00+01:00",
  "keywords": [
    "ZX Spectrum 48K", "ZX BASIC", "SuperBasic",
    "transpilador", "lexer", "parser", "AST",
    "compiladores", "retrocomputación"
  ],
  "articleSection": "Programación"
}
</script>

<!--=========================================================
====== ESTILOS
   • Separa estilos de <pre> y <code> para evitar “doble caja”
   • Scroll horizontal en bloques
=========================================================-->
<style>
/* ===== Bloques de código (tema claro) ===== */
body .post-body pre,
.post-body pre {
  background: #f6f8fa;           /* fondo claro */
  color: #0b1220;                /* texto oscuro legible */
  border: 1px solid #e1e4e8;
  border-radius: 8px;
  padding: 14px 16px;
  margin: 14px 0 18px;
  overflow-x: auto;              /* scroll horizontal en líneas largas */
  line-height: 1.45;
  font: 0.95rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  tab-size: 4;
  white-space: pre;              /* respeta espacios */
}

/* Evitar la “doble caja”: el <code> interior no debe tener fondo ni borde */
body .post-body pre code,
.post-body pre code {
  display: contents;             /* “aplana” el <code> interior */
  background: transparent !important;
  border: 0 !important;
  padding: 0 !important;
  color: inherit;
}

/* ===== Código en línea ===== */
body .post-body code:not(pre code),
.post-body code:not(pre code) {
  background: #f5f7fb;
  color: #111827;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 0.12em 0.33em;
  font: 0.92em ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  white-space: nowrap;
}

/* ===== Contenedor TOC y elementos ya presentes ===== */
.toc-container {
  background: #eef4ff;
  border: 1px solid #c7d7f5;
  padding: 16px 18px;
  margin: 18px 0 28px 0;
  border-radius: 6px;
  font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px 0; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; list-style: disc; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }

blockquote {
  background: #fafcff;
  border-left: 4px solid #c7d7f5;
  margin: 16px 0;
  padding: 10px 12px;
}
.section-meta { color: #4a4a4a; font-style: italic; margin-top: 4px; }

/* Móvil: reduce un poco el tamaño del bloque */
@media (max-width: 520px) {
  body .post-body pre,
  .post-body pre { font-size: 0.9rem; }
}
</style>
<p></p><p style="text-align: center;"><a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor<br /></a></p><hr />

<!--=========================================================
====== TABLA DE CONTENIDOS
=========================================================-->
<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li>
    <li><a href="#motivacion">1. Motivación del Director del Proceso</a></li>
    <li><a href="#pipeline">2. Pipeline completo del transpilador</a></li>
    <li><a href="#dos-tecnicas">3. Las técnicas de lexing/parsing</a></li>
    <li><a href="#flujo">4. Flujo general del proceso (optimizado QL)</a></li>
    <li><a href="#esqueleto">5. Esqueleto del Director (seudocódigo)</a></li>
    <li><a href="#integracion">6. Integración con las siguientes fases</a></li>
    <li><a href="#proximo">7. Próximo paso</a></li>
    <li><a href="#director">APÉNDICE: Director del proceso completo</a></li>
  </ul>
</div>

<hr />

<!--============== CONTENIDO PRINCIPAL ==============-->

<h2 id="intro">Introducción</h2>

<p>
  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 <b>Director del Proceso</b>, responsable
  de coordinar lectura, análisis y generación. Dado que el objetivo final es ejecutar en un <b>Sinclair QL
  real</b> —un entorno con CPU más lenta y RAM muy limitada— adaptamos el diseño para que sea
  <b>rápido</b> y <b>de mínimo consumo de memoria</b>.
</p>

<hr />

<h2 id="motivacion">1. Motivación del Director del Proceso</h2>

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

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

<hr />

<h2 id="pipeline">2. Pipeline completo del transpilador</h2>

<pre><code>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…)</code></pre>

<p>El Director no pertenece a ninguna de estas fases: las coordina a todas.</p>

<hr />

<h2 id="dos-tecnicas">3. Las técnicas de lexing/parsing</h2>

<p>
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 <code>:</code>”), lo que permite estrategias mucho más ligeras.
</p>

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

<p><b>Variantes de “LexAll”</b></p>
<ul>
  <li><b>Tokenizar en memoria:</b> todos los tokens en una lista → muy rápido en PC; consume mucha RAM en máquinas limitadas.</li>
  <li><b>Tokenizar en disco:</b> los tokens se guardan en un fichero intermedio → útil en PC; muy lento en microdrives del QL.</li>
</ul>

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

<p>
Como el objetivo final es ejecutar en QL real, <b><span style="font-size: large;">usaremos la técnica por líneas</span></b>.
El lexer expone <code>LexLine(texto, numLinea)</code> y el parser expone
<code>ParseLine(tokens, numLinea)</code>. Esta técnica maximiza la velocidad y minimiza el uso de memoria.
</p>

<h3>Comparativa rápida</h3>

<table border="1" cellpadding="6" style="border-collapse: collapse; width: 100%;">
  <thead>
    <tr>
      <th style="text-align: left;">Método</th>
      <th style="text-align: left;">Memoria</th>
      <th style="text-align: left;">Velocidad</th>
      <th style="text-align: left;">E/S</th>
      <th style="text-align: left;">Complejidad</th>
      <th style="text-align: left;">Cuándo usar</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><b>Tokenizar en memoria</b></td>
      <td>Alta</td>
      <td>Muy alta</td>
      <td>Mínima</td>
      <td>Baja</td>
      <td>PC con mucha RAM</td>
    </tr>
    <tr>
      <td><b>Tokenizar en disco</b></td>
      <td>Muy baja</td>
      <td>Media (dos pasadas)</td>
      <td>Alta (fichero intermedio)</td>
      <td>Baja</td>
      <td>PC con disco rápido</td>
    </tr>
    <tr>
      <td><b>Tokenizar por líneas</b></td>
      <td>Muy baja</td>
      <td>Alta</td>
      <td>Mínima</td>
      <td>Media</td>
      <td>QL o entornos con recursos limitados</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="flujo">4. Flujo general del proceso (optimizado QL)</h2>

<pre><code>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)</code></pre>

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

<hr />

<h2 id="esqueleto">5. Esqueleto del Director (seudocódigo)</h2>

<p class="section-meta">
El Director optimizado para su ejecución en el QL lee el fichero de origen línea a línea, llama a
<code>Lexer</code>, <code>Parser</code>, etc., guarda lo que ha generado y pasa a la siguiente línea.
Abajo se muestra un esquema básico; en el apéndice encontrarás la versión extendida con gestión de
errores y opción <code>verbose</code>.
</p>

<pre><code>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</code></pre>

<hr />

<h2 id="integracion">6. Integración con las siguientes fases</h2>

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

<hr />

<h2 id="proximo">7. Próximo paso</h2>
<p>
  En la siguiente entrada comenzaremos con el <b>lexer</b> (análisis léxico), usando
  la EBNF final y la estructura <code>LineNumber → SimpleStatement { ":" SimpleStatement }</code>.
</p>
<p>
  En el modo <code>verbose</code> 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.
</p>

<hr />

<h2 id="director">APÉNDICE: Director del proceso completo</h2>

<p>
  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 <code>verbose</code> para salida de información adicional en pantalla:
</p>

<pre><code>// ==================================================================================
//  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</code></pre>

<!--============== FIN ==============-->]]></description>
    </item>
    
    <item>
      <title>ZB2SB. Paso 1. Definir el Lenguaje de origen</title>
      <link>https://old8bits.blogspot.com/2026/03/zb2sb-paso-1-definir-el-lenguaje-de.html</link>
      <guid isPermaLink="true">https://old8bits.blogspot.com/2026/03/zb2sb-paso-1-definir-el-lenguaje-de.html</guid>
      <pubDate>Thu, 12 Mar 2026 12:49:57 -0000</pubDate>
      <description><![CDATA[<!--=========================================================
     CABECERA SEO — old8bits.blogspot.com 
=========================================================-->

<title>Gramática ZX BASIC Spectrum 48K — BNF completa y explicada</title>
<meta content="Gramática formal (BNF/EBNF) del ZX Spectrum 48K BASIC. Guía clara para desarrolladores y creación de transpiladores, basada en el manual oficial de Sinclair/Amstrad e Investrónica." name="description"></meta>

<!--Open Graph (Facebook / LinkedIn / etc.)-->
<meta content="article" property="og:type"></meta>
<meta content="Gramática ZX BASIC Spectrum 48K — BNF completa y explicada" property="og:title"></meta>
<meta content="BNF/EBNF fiel al ZX Spectrum 48K BASIC. Incluye 'Statement' separado y gramática mínima para transpiladores." property="og:description"></meta>
<meta content="https://old8bits.blogspot.com/2026/03/gramatica-zx-basic-spectrum-48k.html" property="og:url"></meta>
<meta content="old8bits" property="og:site_name"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" property="og:image"></meta>

<!--Twitter Cards-->
<meta content="summary_large_image" name="twitter:card"></meta>
<meta content="Gramática ZX BASIC Spectrum 48K — BNF completa" name="twitter:title"></meta>
<meta content="BNF/EBNF del ZX Spectrum 48K BASIC. Ideal para desarrolladores." name="twitter:description"></meta>
<meta content="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/500px-ZXSpectrum48k.jpg" name="twitter:image"></meta>
<meta content="@old8bits" name="twitter:site"></meta>

<!--Datos estructurados (schema.org/TechArticle)-->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "mainEntityOfPage": { "@type": "WebPage", "@id": "https://old8bits.blogspot.com/2026/03/gramatica-zx-basic-spectrum-48k.html" },
  "headline": "Gramática ZX BASIC Spectrum 48K — BNF completa",
  "description": "Gramática formal (BNF/EBNF) del ZX Spectrum 48K BASIC, basada en el manual oficial, con 'Statement' separado.",
  "image": [ "https://blogger.googleusercontent.com/REEMPLAZA_AQUI_LA_URL_DE_LA_IMAGEN_DEL_POST_SPECTRUM.jpg" ],
  "author": { "@type": "Person", "name": "javu61" },
  "publisher": {
    "@type": "Organization",
    "name": "old8bits",
    "logo": {
      "@type": "ImageObject",
      "url": "https://blogger.googleusercontent.com/REEMPLAZA_AQUI_LA_URL_DE_TU_LOGO_O_MINIATURA_DEL_POST_DE_CASSETTE.png",
      "width": 180, "height": 180
    }
  },
  "datePublished": "2026-03-11T10:00:00+01:00",
  "dateModified":  "2026-03-12T10:00:00+01:00",
  "keywords": [ "ZX Spectrum 48K", "ZX BASIC", "BNF", "gramática", "transpilador", "Sinclair BASIC", "retrocomputación" ],
  "articleSection": "Programación"
}
</script>

<p style="text-align: center;">
  <a href="https://old8bits.blogspot.com/2024/11/zb2sb-conversor-de-basic-de-los-zx-al_43.html">Índice de entradas del conversor</a>
</p>
<p style="text-align: center;">Modificado el 12/03/2026, <span style="color: red;">ampliaciones en color rojo</span></p>
<hr />

<!--=========================================================
    ====== TABLA DE CONTENIDOS                         ======
=========================================================-->
<style>
.toc-container {
  background: #eef4ff;
  border: 1px solid #c7d7f5;
  padding: 16px 18px;
  margin: 18px 0 28px 0;
  border-radius: 6px;
  font-family: Arial, sans-serif;
}
.toc-container h3 { margin: 0 0 8px 0; color: #003366; font-size: 1.1rem; }
.toc-container ul { margin: 0; padding-left: 20px; }
.toc-container li { margin: 6px 0; }
.toc-container a { color: #003366; text-decoration: none; }
.toc-container a:hover { text-decoration: underline; }
</style>

<div class="toc-container">
  <h3>📘 Tabla de Contenidos</h3>
  <ul>
    <li><a href="#intro">Introducción</a></li>
    <li><a href="#estructura">1. Estructura de un programa</a></li>
    <li><a href="#tipos">2. Tipos de datos</a></li>
    <li><a href="#expresiones">3. Expresiones</a></li>
    <li><a href="#sentencias">4. Sentencias</a></li>
    <li><a href="#flujo">5. Control de flujo</a></li>
    <li><a href="#datos">6. Datos</a></li>
    <li><a href="#comentarios">7. Comentarios</a></li>
    <li><a href="#funciones">8. Funciones incorporadas</a></li>
    <li><a href="#arrays">9. Arrays</a></li>
    <li><a href="#graficos">10. Gráficos</a></li>
    <li><a href="#colores">11. Colores</a></li>
    <li><a href="#sonido">12. Sonido</a></li>
    <li><a href="#cinta">13. Cinta y ficheros</a></li>
    <li><a href="#minima">14. Gramática mínima para transpiladores</a></li>
    <li><a href="#bnf">15. BNF/EBNF formal (48K)</a></li>
    <li><a href="#refs">16. Referencias</a></li>
  </ul>
</div>

<hr />

<!--============== CONTENIDO PRINCIPAL ==============-->
<h1 id="intro">Gramática del ZX BASIC del Spectrum 48K</h1>

<p>
  Esta entrada ofrece un resumen estructurado de la gramática del
  <b>ZX BASIC del Sinclair ZX Spectrum 48K</b>, 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.
</p>

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

<p style="text-align: justify;">
  <span style="color: red;"><b>NOTA IMPORTANTE:</b> heredado de los ZX80/81 con solo 1 KB disponible, para ahorrar memoria todas las palabras clave se almacenan internamente como <b>tokens de 1 byte</b>. Por tanto, <u>toda sentencia debe comenzar obligatoriamente por un token de palabra clave</u>. No puede comenzar por un identificador. <b>LET es obligatorio siempre</b>. <b>REM</b> solo se reconoce si es exactamente <b>REM</b>.</span>
</p>
<p style="text-align: justify;">
  <span style="color: red;">Estos ajustes son mínimos pero críticos para definir correctamente la gramática y la generación de tokens.</span>
</p>

<hr />

<h2 id="estructura">1. Estructura de un programa</h2>
<p>Un programa en ZX BASIC está compuesto por líneas numeradas:</p>
<pre>&lt;linea&gt; ::= &lt;numero&gt; &lt;espacio&gt; &lt;instruccion&gt;</pre>
<ul>
  <li>Los números de línea van de <b>1 a 9999</b>.</li>
  <li>Una línea puede contener <b>una o varias instrucciones</b> separadas por <code>:</code>.</li>
</ul>

<hr />

<h2 id="tipos">2. Tipos de datos</h2>
<h3>2.1 Tipos básicos</h3>
<ul>
  <li><b>Números</b>: coma flotante de 40 bits.</li>
  <li><b>Cadenas</b>: delimitadas por comillas (<code>"texto"</code>).</li>
  <li>No existen enteros ni booleanos como tipos nativos; la lógica usa 0 para falso y &lt;&gt;0 para cierto.</li>
</ul>

<h3>2.2 Nombres de variables</h3>
<pre>&lt;var_num&gt; ::= &lt;letra&gt; [ &lt;letra_o_digito&gt; ]
&lt;var_str&gt; ::= &lt;letra&gt; [ &lt;letra_o_digito&gt; ] "$"
&lt;var_array&gt; ::= &lt;var&gt; "(" &lt;lista_expresiones&gt; ")"
</pre>
<ul>
  <li>Las variables numéricas y de cadena con el mismo nombre son distintas.</li>
  <li>Las matrices pueden ser numéricas o de cadena.</li>
</ul>

<hr />

<h2 id="expresiones">3. Expresiones</h2>
<h3>3.1 Expresiones numéricas</h3>
<p><b>Precedencia de operadores</b> (de mayor a menor):</p>
<ol>
  <li>Unario: <code>-</code></li>
  <li>Potencia: <code>^</code></li>
  <li>Multiplicación / División: <code>*</code> <code>/</code></li>
  <li>Suma / Resta: <code>+</code> <code>-</code></li>
  <li>Relacionales: <code>=</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>, <code>&lt;&gt;</code></li>
  <li>Lógicos: <code>AND</code>, <code>OR</code>, <code>NOT</code></li>
</ol>
<p>Fuentes: capítulos 3, 7 y 13 del manual.</p>

<h3>3.2 Expresiones de cadena</h3>
<ul>
  <li>Concatenación: <code>"A" + "B"</code>.</li>
  <li>Slicing: <code>A$(n TO m)</code>.</li>
  <li>Longitud: <code>LEN A$</code>.</li>
</ul>
<p>Fuente: capítulo 8 del manual.</p>

<hr />

<h2 id="sentencias">4. Sentencias</h2>
<h3>4.1 Asignación</h3>
<pre>&lt;asignacion&gt; ::= LET &lt;var&gt; = &lt;expresion&gt;</pre>
<p><code>LET</code> es obligatorio en el Spectrum 48K (no puede omitirse).</p>

<h3>4.2 Entrada y salida</h3>
<ul>
  <li><code>PRINT</code></li>
  <li><code>INPUT</code></li>
  <li><code>CLS</code></li>
  <li><code>TAB(n)</code>, <code>AT y,x</code></li>
</ul>
<p>Fuente: capítulos 2 y 15 del manual.</p>

<hr />

<h2 id="flujo">5. Control de flujo</h2>
<h3>5.1 IF</h3>
<pre>IF &lt;expresión&gt; THEN &lt;instrucciones&gt;</pre>
<p>
  Ejecuta todas las instrucciones (separadas por <code>:</code>) hasta fin de línea.
  <b>No existe <code>ELSE</code> en el ZX Spectrum 48K</b>.
</p>

<h3>5.2 Bucles FOR</h3>
<pre>FOR &lt;var&gt; = &lt;expr&gt; TO &lt;expr&gt; [STEP &lt;expr&gt;]
...
NEXT &lt;var&gt;
</pre>
<p>Fuente: capítulo 4 del manual.</p>

<h3>5.3 Subrutinas</h3>
<pre>GOSUB número
RETURN
</pre>
<p>Fuente: capítulo 5 del manual.</p>

<h3>5.4 Saltos</h3>
<ul>
  <li><code>GO TO</code></li>
  <li><code>STOP</code></li>
  <li><code>CONTINUE</code></li>
</ul>

<hr />

<h2 id="datos">6. Datos</h2>
<pre>READ &lt;lista_variables&gt;
DATA &lt;lista_constantes&gt;
RESTORE [número]
</pre>
<p>Fuente: capítulo 6 del manual.</p>

<hr />

<!--=== NUEVA SECCIÓN 7: COMENTARIOS (REM) ===-->
<h2 id="comentarios"><span style="color: red;">7. Comentarios</span></h2>
<p><span style="color: red;">
  En ZX Spectrum 48K, la sentencia <code>REM</code> admite exactamente dos formas:
</span></p>
<ul>
  <li><span style="color: red;"><b>Comentario vacío</b>: <code>REM</code> seguido inmediatamente de fin de línea.</span></li>
  <li><span style="color: red;"><b>Comentario con contenido</b>: <code>REM</code> seguido de un <b>ESPACIO</b> y, a continuación, cualquier número de caracteres (incluidos espacios) hasta fin de línea.</span></li>
</ul>
<p><span style="color: red;">Ejemplos válidos:</span></p>
<pre><code><span style="color: red;">10 REM
20 REM Hola mundo
30 REM  Más espacios al inicio del comentario</span>
</code></pre>
<p><span style="color: red;">Ejemplos inválidos:</span></p>
<pre><code><span style="color: red;">40 REMHola     ; falta el espacio tras REM
50 REM<tab>Hola ; tabulación inmediata tras REM (no permitido)</tab></span>
</code></pre>
<p><span style="color: red;">
  EBNF:
</span></p>
<pre><code><span style="color: red;">ESPACIO      = " ";
EOL          = "\n";
anyCharacter = ? cualquier carácter Unicode ? ;

remStmt =
      "REM"
    | "REM" , ESPACIO , { anyCharacter } ;
</span></code></pre>

<hr />

<h2 id="funciones">8. Funciones incorporadas</h2>
<h3>Matemáticas</h3>
<p>ABS, INT, SGN, SIN, COS, TAN, ASN, ACS, ATN, SQR, EXP, LN, PI …</p>
<h3>Cadenas</h3>
<p>LEN, STR$, VAL, CHR$, CODE</p>
<h3>Sistema</h3>
<p>PEEK, POKE, USR, IN, OUT, RND, RANDOMIZE</p>
<p>Fuente: capítulos 9, 10, 11, 14 y 23 del manual.</p>

<hr />

<h2 id="arrays">9. Arrays</h2>
<pre>DIM A(10)
DIM N$(5)
</pre>
<ul>
  <li>Índices desde <b>1</b> hasta el tamaño declarado (arrays base 1).</li>
  <li>Acceso: <code>A(1), A(5), N$(1)…</code></li>
</ul>

<hr />

<h2 id="graficos">10. Gráficos</h2>
<ul>
  <li><code>PLOT x,y</code></li>
  <li><code>DRAW dx,dy</code></li>
  <li><code>CIRCLE x,y,r</code></li>
  <li><code>POINT x,y</code></li>
</ul>
<p>Fuente: capítulo 17 del manual.</p>

<hr />

<h2 id="colores">11. Colores</h2>
<ul>
  <li><code>INK n</code></li>
  <li><code>PAPER n</code></li>
  <li><code>FLASH n</code></li>
  <li><code>BRIGHT n</code></li>
  <li><code>INVERSE n</code></li>
  <li><code>OVER n</code></li>
  <li><code>BORDER n</code></li>
</ul>
<p>Fuente: capítulo 16 del manual.</p>

<hr />

<h2 id="sonido">12. Sonido</h2>
<pre>BEEP duracion, tono</pre>
<p>Fuente: capítulo 19 del manual.</p>

<hr />

<h2 id="cinta">13. Cinta y ficheros</h2>
<ul>
  <li><code>SAVE</code></li>
  <li><code>LOAD</code></li>
  <li><code>MERGE</code></li>
  <li><code>VERIFY</code></li>
</ul>
<p>Fuente: capítulo 20 del manual.</p>

<hr />

<h2 id="minima">14. Gramática mínima para transpiladores</h2>
<pre>&lt;program&gt; ::= { &lt;line&gt; }

&lt;line&gt; ::= &lt;number&gt; &lt;Statement&gt;

&lt;Statement&gt; ::= &lt;SimpleStatement&gt; { ":" &lt;SimpleStatement&gt; }

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

<hr />

<h2 id="bnf">15. BNF/EBNF del ZX BASIC del Spectrum 48K (versión corregida)</h2>

<h3>1) Léxico</h3>
<pre>letter        = "A".."Z" | "a".."z"
digit         = "0".."9"
<span style="color: red;">nonZeroDigit  = "1".."9"</span>
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 ]

<span style="color: red;">ESPACIO      = " "</span>
<span style="color: red;">EOL          = "\n"</span>
<span style="color: red;">anyCharacter = ? cualquier carácter Unicode ?</span>

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

<span style="color: red;">
; REM solo se reconoce como palabra clave si el token es exactamente "REM".
; Un identificador NO puede iniciar una sentencia.
</span>
</pre>

<h3>2) Estructura del programa</h3>
<pre>program       = { line }

line          = lineNumber , Statement
<span style="color: red;">lineNumber    = LineNumberToken</span>
<span style="color: red;">LineNumberToken = nonZeroDigit , [ digit ] , [ digit ] , [ digit ]</span>
<span style="color: red;">(* Rango efectivo: 1..9999; token específico generado por el lexer *)</span>
</pre>

<h3>3) Statement y SimpleStatement</h3>
<pre>Statement     = SimpleStatement , { ":" , SimpleStatement }

<span style="color: red;">; SimpleStatement debe comenzar por token de palabra reservada</span>

SimpleStatement =
                   <span style="color: red;">letStmt</span>
                 | printStmt
                 | inputStmt
                 | ifStmt
                 | forStmt
                 | nextStmt
                 | gotoStmt
                 | gosubStmt
                 | returnStmt
                 | stopStmt
                 | continueStmt
                 | readStmt
                 | dataStmt
                 | restoreStmt
                 | dimStmt
                 | clsStmt
                 | graphicStmt
                 | colorStmt
                 | soundStmt
                 | tapeStmt
                 | remStmt
</pre>

<h3>4) Expresiones</h3>
<pre>expr          = logicOr
logicOr       = logicAnd , { "OR" , logicAnd }
logicAnd      = relation , { "AND" , relation }

relation      = arithExpr , [ relOp , arithExpr ]
relOp         = "=" | "&lt;&gt;" | "&lt;" | "&gt;" | "&lt;=" | "&gt;="

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

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

primary       = numLiteral
              | strLiteral
              | varRef
              | funcCall
              | "(" , expr , ")"
</pre>

<h3>5) Variables y arrays (base 1)</h3>
<pre>varRef        = arrayRef | var

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

indexExpr     = expr
</pre>

<h3>6) Funciones</h3>
<pre>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) , ")"
</pre>

<h3>7) Sentencias (detalle)</h3>
<pre><span style="color: red;">remStmt   = "REM"
          | "REM" , ESPACIO , { anyCharacter }
</span>&nbsp;</pre><pre><span style="color: red;">letStmt   = "LET" , ( varRef | var ) , "=" , expr</span></pre><pre>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
</pre>

<hr />

<h2 id="refs">16. Referencias</h2>
<ul>
  <li>
    <b>Manual canónico (World of Spectrum)</b> — Índice completo del
    <i>ZX Spectrum BASIC Programming Manual</i> (Sinclair/Amstrad).
    <a href="https://worldofspectrum.org/ZXBasicManual/" rel="noopener" target="_blank">https://worldofspectrum.org/ZXBasicManual/</a>
  </li>
  <li>
    <b>World of Spectrum — Capítulo 13: Conditions</b> — Operadores lógicos y prioridad (AND/OR/NOT, relacionales).
    <a href="https://worldofspectrum.org/ZXBasicManual/zxmanchap13.html" rel="noopener" target="_blank">https://worldofspectrum.org/ZXBasicManual/zxmanchap13.html</a>
  </li>
  <li>
    <b>Introducción Sinclair (Statement LET)</b> — Especifica que en 48K <code>LET</code> <u>no</u> puede omitirse.
    <a href="https://worldofspectrum.org/ZXSpectrumIntroduction/chapter_five.html" rel="noopener" target="_blank">https://worldofspectrum.org/ZXSpectrumIntroduction/chapter_five.html</a>
  </li>
  <li>
    <b>Manual de Investrónica (español)</b> — Edición en HTML con capítulos de cadenas, matrices, gráficos y colores (consulta rápida).
    <a href="https://manuales.speccy.org/basic_gomas/" rel="noopener" target="_blank">https://manuales.speccy.org/basic_gomas/</a>
  </li>
</ul>]]></description>
    </item>
    
  </channel>
</rss>