Desarrollando un intérprete de lenguaje BASIC en 2025
Recientemente tuve la oportunidad de tener una consola Intellivision II junto con el ECS (Entertainment Computer System) y su teclado. Encontré y tecleé un juego
Bomb Run 1, usando su lenguaje BASIC integrado, y me quedé sorprendido de
ver lo lento que es.
Parece que Mattel Electronics solo desarrolló el ECS para evitar pagar una multa diaria de $10,000 dólares al gobierno de los Estados Unidos porque habían anunciado un componente de teclado que no estaba disponible. Así que desarrollaron el ECS en secreto, mientras ponían todo su dinero en el fracaso del Keyboard Component que era una computadora completa basada en 6502 alrededor del Intellivision con un lenguaje BASIC oficial de Microsoft.
Después de poner el ECS a la venta, Mattel se olvidó completamente de este. Es una lástima porque el teclado es razonable, luce bien, y pudo ser una plataforma de BASIC muy buena para principiantes.
De todas formas, la velocidad de tortuga me intrigó ya que estaba muy convencido de que el procesador del Intellivision podía hacer punto flotante más rápido, así que ¿Por qué no escribir mi propio intérprete de BASIC extendido?
En lo que resta de este artículo, el BASIC Mattel ECS original será llamado simplemente ECS BASIC.
Trasfondo del sistema
La implementación de mi intérprete BASIC extendido es para un procesador General Instruments CP1610. Este es un procesador de 16 bits y 3 voltajes introducido en 1975, con un parecido al PDP-11. Sin embargo, General Instruments no permitía segundas fuentes, solo quería compras grandes, e ignoraba pedidos pequeños (básicamente se cortó las manos), así que este procesador solo fue usado ampliamente en el Mattel Intellivision, y cesó producción en 1985 al mismo tiempo que fracasó el Intellivision.
Una documentación completa del conjunto de instrucciones está disponible junto con el emulador jzintv que puede ser descargado de
http://spatula-city.org/~im14u2c/intv/.
Usaré las 8K palabras entre $5000-$6fff para el lenguaje BASIC, e ignoraré por completo el EXEC (la BIOS de Intellivision) y las ROM del ECS (ni siquiera me molesté en desactivarlas).
Nunca codifiqué un lenguaje BASIC completo antes, porque no era necesario. En los ochentas, tenía acceso a un BASIC Z80 que ya había sido portado a la computadora homebrew que usaba, y en los noventas, me las arreglé para poner punto flotante en el
Tiny BASIC de Li-Chen Wang, hice algunas instrucciones nuevas, y también intenté hacer un BASIC tokenizado, pero nunca llegué muy lejos.
El núcleo de punto flotante
Comencé el 17-sep-2025 codificando una subrutina de suma de punto flotante. No tenía un formato en mente, solo tenía que ser 32 bits porque los registros del CP1610 son de 16 bits, así que dos registros se ajustan muy bien para contener un número de punto flotante, y otros dos para un segundo operando.
El formato se decidió viendo que tan fácil era extraer el signo, el exponente y la mantisa con operaciones de 16 bits, ya que las operaciones de 8 bits son lentas. Esto descartó automáticamente el formato compatible con IEEE-754, y decidí usar un formato con una mantisa de 24 bits en los bits alto, seguidos por el bit de signo, y el exponente en los 7 bits bajos. El exponente es un bit más pequeño que el clásico IEEE-754, pero obtenemos un bit extra de precisión.
El código para extraer la mantisa es bastante corto:
ANDI #$FF00,R1 ; Elimina el signo y el exponente.
SETC
RRC R0,1 ; Inserta el uno fijo en la parte alta de la mantisa.
RRC R1,1 ; Ahora tenemos la mantisa de 25 bits,
; alineada en el bit más alto.
No definí números denormalizados, ni infinto ni NaN (Not a Number, o No es un Número), porque esto no era admitido en intérpretes BASIC de los ochentas.
Fue fácil hacer la rutina de resta una vez que funcionó la rutina de suma, porque solo se necesita invertir el bit de signo en el segundo operando.
Hice después la rutina de multiplicación, pero tropecé con un problema donde la mantisa sobrepasaba su espacio. Simplemente agregué un código muy complicado para mover la mantisa por un bit. Fue varios días después cuando lo estudié, y descubrí que se puede tener un resultado de x+y-1 bits o x+y bits (donde x es el número de bits significativos en el primer operando, y y es el número de bits significativos en el segundo operando) y entonces pude optimizarlo simplemente insertando un cero extra a la izquierda para dejar espacio al acarreo.
Por supuesto, hice un pequeño programa de prueba para checar la validez de las operaciones aritméticas con varios casos. Me tomó cuatro días codificar una librería de punto flotante completamente funcional.
La interface
No podía comenzar un intérprete BASIC sin una rutina de lectura de teclado, y una salida estilo terminal. Afortunadamente, Joe Zbiciak (intvnut) ya había desarrollado rutinas para leer el teclado ECS, y las integré con un encabezado ROM, añadiendo manejo de terminal para ilustrar letras, rolar la pantalla y mover el cursor.
Con todo esto integrado ya tenía una terminal tonta funcionando, tecleas cualquier cosa en el teclado, y obtienes lo mismo ilustrado en la pantalla. Esto fue el 19-sep-2025.
El teclado ECS.
Las tripas
El procesador CP1610 no puede manejar directamente la memoria interna como bytes, en lugar de eso todo es por palabras completas. Tuve que tomar esto en cuenta para mi representación BASIC tokenizada. Un Intellivision estándar no tiene suficiente memoria para un intérprete BASIC, así que el ECS BASIC incluye 2K de RAM de 8 bits.
Sin embargo, hace unos años el cartucho JLP-Flash fue creado y provee de 8K de RAM de 16 bits en el área $8000-$9fff, y esto es excelente para mi BASIC extendido.
Cuando hablo de tokenización, significa que todas las palabras reservadas del lenguaje son representadas con un token. Esto acelera la ejecución del lenguaje, porque no tiene que comparar palabras cada vez.
Mi primera versión de la representación interna para las líneas BASIC fue el número de línea como una palabra, seguido por un apuntador a la siguiente línea, seguido por el código BASIC tokenizado para la línea, y terminado con una palabra cero.
Mientras codificaba las rutinas de inserción de lines el 22-sep-2025, descubrí que el apuntador a la siguiente línea no era una buena idea, porque necesita mover todos los apuntadores después de una inserción de líneas. En lugar de eso, convertí el apuntador en un tamaño (el número de palabras usadas por la línea tokenizada) Esto permitió un código muy compacto para saltar sobre líneas:
INCR R4 : Evita el número de línea.
ADD@ R4,R4 ; Añade el tamaño tokenizado al apuntador actual.
; ¡Listo! Saltó sobre la línea.
Con las rutinas de inserción de líneas completas, decidí implementar la rutina de tokenización de BASIC. Opté por evitar la tokenización byte por byte, y en lugar de eso usé una palabra. Por supuesto, es espacio desperdiciado si usas cadenas, pero es más rápido en ejecución. Los números de token empiezan en $0100. Solo quedaba interfazar la entrada con las rutinas nuevas.
Decidí leer el texto directamente de la pantalla, todo sin planear, y probablemente con bugs, pero ha funcionado hasta el momento. Tal vez después lo extienda a un editor de pantalla completa.
keywords:
DECLE ":",0 ; $0100
DECLE "LIST",0
DECLE "NEW",0
DECLE "CLS",0
DECLE "RUN",0 ; $0104be
DECLE "STOP",0
DECLE "PRINT",0
DECLE "INPUT",0
DECLE "GOTO",0 ; $0108
DECLE "IF",0
DECLE "THEN",0
DECLE "ELSE",0
DECLE "FOR",0 ; $010C
DECLE "TO",0
DECLE "STEP",0
DECLE "NEXT",0
DECLE "GOSUB",0 ; $0110
Parte de la tabla de tokens.
Ejecución
Ahora podía editar, corregir y borrar líneas de código BASIC. El siguiente paso lógico fue la ejecución del programa, implementé RUN leyendo cada línea del programa secuencialmente, y cada token encontrado escogía directamente el comando a ejecutar.
Mi primer programa fue simplemente 10 CLS y estuve muy feliz cuando teclee RUN y la pantalla se borró.
Esto fue seguido prontamente por PRINT y GOTO. Donde PRINT solo podía poner una cadena en la pantalla, y GOTO cambiaba el flujo de ejecución. Agregé una comprobación de la tecla Esc para salir de bucles infinitos.
También estaba muy impaciente por ver si mi lenguaje BASIC extendido era más rápido que el ECS BASIC, así que decidí implementar IF, y un pequeño analizador de expresiones que permitía los operadores relacionales y los operadores aritméticos básicos (+, -, * y /), junto con números y variables.
Los números simplemente se leen como enteros y se convierten a formato de punto flotante, mientras que las variables usan 26 palabras dobles de espacio para cubrir las variables A a la Z.
Para poder crear un bucle se requirió implementar asignación de variables.
10 A=1
20 PRINT "Hello"
30 A=A+1
40 IF 6>A THEN 20
El programa BASIC convertido a tokens.
Por alguna razón no podía teclear el operador < con el teclado ECS emulado. Después descubrí que a intvnut le faltaba el caracter en la tabla de Shift, y fue fácil corregirlo.
Era pasada la medianoche cuando por fin pude probar el
benchmark. No tenía todavía una sentencia
FOR, así que la repliqué con incremento y comparación.
Lo corrí, y me sorprendí de descubrir que tomaba solo 15 segundos. ¡En el ECS BASIC toma 210 segundos! Hay capturas de pantalla de los programas en el git.
Más curiosidades del punto flotante
Esta no era la primera vez que programa un paquete de punto flotante. El primero fue para una computadora basada en Z80, y no recuerdo si estaba completo, si tenía bugs, o si lo llegué a usar. Lo que puedo recordar es que nunca pude hacer una rutina correcta para ilustrar números de punto flotante. Me quedé atrapado en una simple conversión a entero, y mostrar el entero.
Ilustrar un número seguido de fracción y exponente, para mí estaba más cerca de la magia negra que nada. Creía que una sola rutina hacía todo, pero estaba equivocado. Se me iluminó la mente leyendo un manual de BASIC de Commodore 64, dice algo como que los números en un rango se ilustran completos, mientras que en todos los otros casos el número se ilustra en formato de exponente.
Esto activó un patrón en mi mente: Si el entero completo cabe en la mantissa, ilustrarlo junto con la pequeña fracción, y si el número no cabe, hacerlo más grande o pequeño para que quepa en el entero, y éste se puede ilustrar en formato de exponente.
El algoritmo es como sigue:
- La mantisa de 25 bits permite enteros de 0 a 33554431. Lo limitamos al mayor entero que sea todo nueves, o 9999999.
- Si el número de punto flotante es menor que 10,000,000 entonces la parte entera es ilustrada, junto con 2 dígitos de fracción.
- Si el número de punto flotante es menor que 0.01 entonces se multiplica hasta que alcanza el rango 1,000,000 - 9,999,999. El primer dígito será la parte entera, y los siguientes dígitos seran la parte fraccional, y el exponente se mostrará adelante.
- Si el número de punto flotante es mayor que 9,999,999 entonces se divide por 10 hasta que cabe en el rango. Igual ilustra como en el paso 3.
Y así es como treinta años después, descubrí que imprimir números de punto flotante no es tan oscuro como creí, pero si tiene mucha magia.
Núcleo completado
Las sentencias GOSUB/RETURN permiten crear pequeñas subrutinas, y estas tienen su propia pila para mantener los datos de donde retornar (un apuntador y el número de línea)
El bucle FOR/NEXT es uno de los más conocidos del núcleo del lenguaje BASIC. Implementarlo requirió un rediseño de mi bucle de ejecución, porque lo hacía línea por línea, pero el NEXT cambiaba la línea, y en la siguiente sentencia perdía la pista y volvía a la línea después del NEXT.
Los bucles también requieren su propia pila, pero incluyendo la variable contadora, un apuntador a la expresión del TO, y un apuntador a la expresión del STEP (5 palabras en total).
La sentencia >RUN fue reemplazada por código que corre secuencialmente sobre los tokens, y salta los encabezados de línea. De esta forma es más fácil cambiar el flujo de ejecución a un nuevo token.
También añadí el operador de negación (requerido para STEP -1) y algunas funciones como INT, ABS, SGN, y RND. La función RND en particular permite crear pequeños juegos para adivinar números y así.
Checando contra el ECS BASIC, solo me faltaba READ, DATA, y REM. Así que los implementé y de paso agregué RESTORE.
DIM para crear arreglos fue relativamente fácil, y ajusté todos los caminos de acceso a variables del intérprete para que cualquier acceso indexado fuera lo mismo que acceder una variable normal.
Caminos divergentes
En este punto mi lenguaje BASIC extendido ya era ordenes de magnitud más rápido que el ECS BASIC, y podía ser usado para escribir pequeños juegos de texto (bueno, usando solo números)
Sin embargo, no manejaba todavía los controladores, sonido, gráficos y sprites. El ECS BASIC tiene sentencia para esto, pero los sprites no pueden ser definidos y en lugar de eso se "traen" de un cartucho de juego. Por supuesto, el usuario está limitado a los gráficos del cartucho. También posicionar sprites se hacía con múltiples asignaciones de variables en un estilo arreglo.
Para mi BASIC extendido me decidí por sentencias avanzadas calcadas de las usadas en mi
lenguaje compilado IntyBASIC pero no exactamente iguales:
- MODE 0,0 pone el modo de color de pila.
- MODE 1 pone el modo de fondo/tinta.
- DEFINE 0,"55AA55AA55AA55AA" define caracteres GRAM.
- COLOR pone el color usado en PRINT.
- SPRITE para ilustrar un sprite en la pantalla.
- WAIT para esperar el próximo cuadro de video
- SOUND para acceder el chip de sonido.
- STICK(0) para leer las 16 direcciones del disco.
- TRIG(0) para leer los botones.
- KEY(0) para leer el pad numérico.
- BK(0-239) para acceder la pantalla.
Una vez estos fueron implementados, comencé a codificar un juego mínimo para probar el intérprete, y lo llamé UFO Invasion. Por supuesto, encontré algunos pequeños bugs en mi intérprete y los corregí.
El juego funcionaba y a una velocidad razonable. ¿Qué tal probarlo en hardware real? Cargué el intérprete en mi cartucho LTO-Flash y conecté mi ECS.
Mi primer intento fallaba continuamente. Me taré media hora buscando errores, hasta que noté que cualquier cosa crasheaba el intérprete. Había olvidado activar la RAM extra del cartucho JLP. ¡Y funcionó!
Teclear el programa fue difícil, ya que el teclado repetía teclas. Esto sucede cuando se lee demasiado rápido el teclado, porque es tan rápido que se descubre que el contacto de las teclas no es perfecto. Tuve que añadir una pequeña espera antes de leer el teclado, y solucionó la mayor parte de los problemas.
Al final, mi intérprete de BASIC extendido fue codificado en seis días. Creo que es más rápido si estás disfrutando programarlo.
10 CLS:REM UFO INVASION. NANOCHESS 2025
20 DEFINE 0,"183C00FF007E3C000018183C3C7E7E000000183C3C3C3C7EFF2400"
50 x=96:w=0:v=0:u=0:t=159
60 SPRITE 0,776+x,344,2061
70 SPRITE 1,776+v,256+w,2066
80 SPRITE 2,1796+t,256+u,6149
90 WAIT:c=STICK(0)
100 IF c>=3 AND c<=7 THEN IF x<152 THEN x=x+4
110 IF c>=11 AND c<=15 THEN IF x>0 THEN x=x-4
120 IF w=0 THEN SOUND 2,,0:IF STRIG(0) THEN v=x:w=88
130 t=t+5:IF t>=160 THEN t=0:u=INT(RND*32)+8
140 IF w THEN SOUND 2,w+20,12:w=w-4:IF ABS(w-u)<8 AND ABS(v-t)<8 THEN
t=164:w=0:SOUND 3,8000,9:SOUND 1,2048,48
150 GOTO 60
UFO Invasion corriendo en un Mattel Intellivision ECS, y un listado parcial del juego.
¿Qué sigue?
Una gran diferencia contra el BASIC "estándar" es la falta de cadenas. En el ECS BASIC, se puede leer una cadena del teclado usando GET, y ponerla en pantalla usando PUT, pero eso es todo.
Añadir el apoyo de cadenas estándar significa que podría correr algunos programas de procesamiento de textos como Eliza en BASIC, y otros pequeños juegos podrían ser fácilmente convertidos.
Esto era una de las cosas portables del lenguaje BASIC en su tiempo, y era muy usado en muchos libros de una forma que los programas estaban escritos con ese BASIC núcleo en mente, y podían ser introducidos en casi cualquier computadora con un intérprete decente.
El código fuente esta disponible en
https://github.com/nanochess/ecsbasic. Intenté liberarlo lo más pronto posible, para que puedas ver como iba creciendo en los commits.
¡Disfrutalo!
¿Te encantó este artículo? Invitame un café en ko-fi
Ligas relacionadas
Última actualización: 28-sep-2025