07 septiembre 2008

Un compilador sencillo paso a paso (17 - Alguna función predefinida)

Hasta ahora podíamos hacer asignaciones sencillas, como a := 2, pero en el mundo real hay mucho más que eso: podemos encontrarnos con operaciones en una asignación,como posicion := columna * 40 + fila, o con valores obtenidos de funciones, como en tecla := readkey.

Las expresiones matemáticas las trataremos en el siguiente acercamiento. Ahora vamos a añadir alguna función predefinida, como el anterior "readkey", que nos permita ver qué tecla ha pulsado el usuario.

Antes hacíamos asignaciones a variables a partir de valores que eran constantes numéricas o constantes con nombre, de modo que se podía solucionar apenas en dos pasos:

LD A, valor
LD (variable), A


Ahora el primer paso cambiará ligeramente: guardará el valor en el registro A, pero este valor puede provenir de diversos sitios: será "LD A, (variable)" si viene de una variable, o pasaremos el testigo a la función si corresponde. Muchas de las funciones predefinidas para el CPC devuelven su resultado en el registro A, como CALL &BB18, que espera a que se pulse una tecla; otras devuelven el resultado en otros registros, como HL; y tendremos que extraer lo que nos interese con órdenes como "LD A, H"; otras activan flags como C o Z, y entonces tendremos que usar estructuras de control como RET Z: nos podría interesar emplear secuencias como "LD A, 1 - RET NZ - LD A, 0 - RET" (cargar 1 en A y volver si el flag Z no está activo; en caso contrario, cargar 0 en A y volver).


Vamos a aprovechar para hacer otra mejora: los programas que genera nuestro compilador tienen un tamaño relativamente grande, si tenemos en cuenta que la arquitectura de destino es un ordenador que deja libre poco más 40 Kb para programas de usuario. Parte de ese tamaño (cerca de la mitad) se debe a la cantidad de comentarios que se incluyen en el código generado. Estos comentarios son útiles a la hora de depurar fallos de generación de código, pero deberían ser innecesarios (o casi) cuando el compilador esté terminado. Por eso, es razonable añadir la posibilidad de eliminar los comentarios en el código de destino. Para eso, cambiaremos la forma de generar código. Antes se usaban cosas "muy artesanales" como:


  writeln( ficheroDestino, lineaActual,' DATA CD,5A,BB: '' CALL &BB5A - WRITECHAR' );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 3;


El código alternativo actual podría ser una función "genCodigo" (generar codigo), que permita crear una orden DATA con una secuencia de hasta 3 bytes, que es lo más habitual en nuestra arquitectura de destino. Por si alguna orden es más corta, el primer parámetro será la longitud real (si es menor que tres, se despreciaría el valor de alguno de los parámetros). El último parámetro será el comentario, de forma que se pueda omitir con facilidad:

genCodigo(3, 'CD', '5A', 'BB', 'CALL &BB5A - WRITECHAR' );


De paso, vamos a añadir alguna posibilidad mas, como que WriteChar permita usar variables, no sólo textos prefijados, aprovechando la misma rutina de lectura de valores que hemos utilizado en las asignaciones. Ahora serán válidas también expresiones como

writeChar(letra);


(aunque como sólo tenemos un tipo de variables, byte, esa variable "letra" debería ser de tipo "byte", todavía no de tipo "char", que es lo que sería más razonable).

Con estos cambios ya se podrán hacer secuencias de órdenes como:


  writeString('Pulsa una tecla: ');
tecla := readkey;
writeString('Has pulsado: ');
writeChar(tecla);


Como siempre, para más detalles, todo el código está en la página del proyecto en Google Code:

http://code.google.com/p/cpcpachi/