El intérprete TINY BASIC es - en palabras de su creador Dennis Allison -
algo así como una cebolla. Contiene un programa interno en lenguaje
máquina (ML) que interpreta un segundo programa escrito en un lenguaje
intermedio (IL), que a su vez interpreta el programa BASIC, y así
sucesivamente. Este documento describe dicho lenguaje intermedio y la
máquina virtual que lo ejecuta.
El intérprete IL es un intérprete puro en el sentido de que todo el
intérprete BASIC se implementa dentro de los límites del lenguaje. No
existen escapes deus ex machina al lenguaje máquina, salvo la bien
definida llamada a subrutina en lenguaje máquina. El lenguaje es
básicamente el mismo que el definido por Dennis Allison en el Diario del
Dr. Dobb y PCC.
La mayoría de las instrucciones del IL ocupan un byte de código. Algunas
instrucciones pueden ir seguidas de uno o más bytes de datos inmediatos,
y hay dos instrucciones de salto que tienen realmente una longitud de
dos bytes.
El intérprete en sí no utiliza almacenamiento de variables. Los cálculos
se realizan en una pila de expresiones, de modo que todos los
procedimientos son recursivos. El intérprete tiene acceso a la memoria
(página 00) para el almacenamiento de datos, pero se aprovecha poco de
esta capacidad.
El código IL es autorelativo. Es decir, todos los saltos son relativos
al inicio del intérprete IL. Por lo tanto, el código puede trasladarse a
otra parte de la memoria sin necesidad de reensamblarlo. Las
ramificaciones condicionales son relativas a la PC y solo se ramifican
hacia adelante hasta un desplazamiento máximo de 31 bytes. Una
ramificación incondicional tiene un rango de 31 bytes hacia adelante o
hacia atrás. Dado que el intérprete suele ser bastante pequeño, esto no
supone una limitación importante. Solo hay dos o tres lugares donde se
necesitaría una ramificación condicional más larga; estos se adaptan
mediante la ramificación a un salto. Los saltos tienen un espacio de
direcciones de 11 bits, o 2 KB desde el inicio del código IL.
Dos de las instrucciones incluyen una cadena de texto literal como parte
del código. Esta cadena sigue al código de operación y tiene una
longitud arbitraria. El final de la cadena se indica mediante el octavo
bit del último byte, que se establece en uno. Dado que generalmente se
asume que el texto es ASCII, un código de 7 bits, esta es una forma
razonable de ahorrar espacio.
Hay dos pilas en la máquina virtual. La pila computacional o de
expresiones ya se mencionó. La otra pila es la pila de control, que se
utiliza para almacenar las direcciones de retorno de las subrutinas.
Esta misma pila de control se utiliza para los retornos de subrutinas en
tres lenguajes: BASIC, IL y ML. Por lo tanto, se requiere cierto cuidado
en el mantenimiento de esta pila. El intérprete de ML se encargará de
sus requisitos colocándolos en la parte superior de la pila, y un
parámetro especial ("SPARE") en el programa principal define la cantidad
máxima de espacio que se deja en la pila para este propósito. El
desbordamiento de esta parte de la pila no es detectable, por lo que es
esencial que el espacio reservado sea lo suficientemente grande. Dado
que el intérprete ML no es recursivo, esto es razonablemente seguro,
siempre que los requisitos de la pila de E/S sean conocidos y limitados.
Debajo de la pila ML se encuentra la pila IL. Las llamadas a subrutinas
en la IL tienen su dirección de retorno insertada en esta pila. El
intérprete ML comprueba si hay desbordamiento de pila midiendo la
distancia entre la parte superior de la pila IL y el final del programa
BASIC; si esta es menor que el parámetro SPARE, se considera que la pila
se ha desbordado.
Debajo de la pila IL se encuentra la pila BASIC. Esta contiene los
números de línea de las líneas GOSUB a medida que se ejecutan. Hay dos
instrucciones en la IL para acceder al elemento superior de esta pila
(es decir, una inserción y una extracción). Para que estas instrucciones
funcionen correctamente, es esencial que la pila IL esté vacía. En el ML
no se realiza ninguna comprobación para esta condición, y es
responsabilidad del programa IL asegurar esta forma de integridad de la
pila. En otras palabras, los GOSUB y RETURN del lenguaje BASIC no pueden
procesarse dentro de una subrutina IL.
La interpretación del programa BASIC está ligada a un puntero (invisible
para el IL) que apunta al carácter actual en la línea actual. El IL no
tiene control directo sobre este puntero, pero varias de las
instrucciones IL hacen que avance o modifique. En particular, puede
modificarse para que apunte al inicio de otra línea BASIC para
implementar las operaciones de control de secuencia BASIC (GOTO, GOSUB,
RETURN). También puede intercambiarse con su dual lógico, un puntero al
carácter actual en el búfer de línea de entrada. Esto permite que el
mismo intérprete opere sobre el programa BASIC almacenado en memoria o
sobre una sentencia de ejecución directa en el búfer de línea.
Existen varias operaciones IL que pueden generar una condición de error.
Todos los errores abortan la ejecución IL e imprimen en la consola el
contador del programa IL donde se produjo el error. Si se activó el
indicador de ejecución del programa, el número de línea BASIC accedido
más recientemente también se escribe en el mensaje. La dirección
relativa (relativa al inicio del IL) se convierte en el número de error
en el mensaje de error. Dado que solo es posible un error en la mayoría
de las operaciones, esto proporciona una identificación única del
problema.
La dirección IL se imprime en decimal y representa la dirección del
siguiente byte que se habría ejecutado de no ser por el error.
Existe una operación con dos posibles modos de fallo; para uno de ellos,
la dirección IL se decrementa antes de imprimirse para distinguirla del
otro. Las paradas por error pueden solicitarse explícitamente en el
programa IL mediante la ejecución de una rama con desplazamiento a cero.
Después de escribir las direcciones de error, el intérprete de ML borra
las pilas de ML e IL (¡pero no la pila BASIC!) y reinicia el intérprete
de IL en la dirección relativa 0. No se produce ningún cambio, excepto
que se borra el indicador de ejecución, lo que pone al intérprete en
modo comando. Cuando se reconoce la condición de interrupción al avanzar
a la siguiente instrucción BASIC, el intérprete de lenguaje de
programación (ML) la trata como una condición de error, tras forzar el
contador del programa IL a cero relativo.
El intérprete de ML mantiene un indicador para distinguir el modo de
ejecución del programa de la ejecución directa de la instrucción (modo
comando). Al avanzar a la siguiente instrucción (una instrucción IL), se
examina este indicador y, si se está en modo comando, el programa IL se
reinicia desde el principio.
Si el indicador está en modo de ejecución, la ejecución se reanuda en la
dirección IL guardada por la instrucción Execute. Es importante que la
instrucción Execute se dé en la IL antes de cualquier avance de la
siguiente instrucción BASIC; sin embargo, una vez realizado, no hay
ninguna restricción (es decir, la dirección guardada nunca se pierde).
La condición de interrupción se prueba solo durante la ejecución del
avance de la instrucción (Next), de modo que la reanudación de un
programa interrumpido no deje lagunas computacionales. La condición de
interrupción también se prueba durante una operación LIST, pero solo
para abortar el listado; si la operación LIST se produjo durante la
ejecución del programa (es decir, con el indicador de modo RUN
activado), se requiere una segunda condición de interrupción para
finalizar el programa.
A continuación, se detalla el funcionamiento de cada uno de los códigos
de operación IL. Cada descripción incluye el código de operación
hexadecimal y el mnemónico reconocido por el ensamblador.
No todos los códigos de operación están definidos. Algunos se han
incorporado a funciones no utilizadas; otros están reservados para una
posible expansión futura y se ejecutan como NOP.