TBIL -- El Lenguaje Intérprete de TINY BASIC 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.