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/

01 septiembre 2008

Un compilador sencillo paso a paso (16 - Validando y ampliando)

Antes de avanzar más, llega el momento de probar la corrección de todo lo realizado, para evitar despistes que sean cada vez más difíciles de encontrar y más costosos de corregir.

La forma habitual es crear una serie de casos de prueba cuyo resultado sea conocido. Para nosotros se trataría de fuentes de prueba que permitan comprobar que se reconoce correctamente el lenguaje de origen y que se convierte correctamente al lenguaje de destino.

Deberíamos poner bajo prueba cada una de las posibilidades del lenguajes. Nosotros lo haremos de forma un poco menos exhaustiva (y, por tanto, menos fiable), con un único fuente que utilice todo lo que hemos implementado: constantes, variables, saltos, escritura de caracteres y de cadenas, cambio de modo de pantalla y de colores, comprobación de condiciones ciertas y falsas, repeticiones de varios tipos...

El fuente de prueba ha sido éste:


program ej16; { Ejemplo 16 de CpcPaChi }

(* Prueba todas las funcionalidades de
CpcPaChi disponibles hasta la version
0.16 *)

const
MODO = 1;

var
i, j, k: byte;

label
escribirSinCambiarColor;

procedure inicializa;
begin
cpcMode(MODO);
cpcInk(0,BLACK);
cpcInk(1,BRIGHTCYAN);
cpcInk(2,BRIGHTBLUE);
cpcInk(3,BLUE,CYAN);
{ paper(0);}
pen(2);
end;


begin
inicializa;

locate(2,2);
writeString('Prueba de FOR: 3 asteriscos ');
for i := 1 to 3 do
begin
pen(i);
writeChar('*');
end;
pen(1);
locate(2,3);
writeString('Prueba de IF: 3 es ');
i := 3;
if i=3 then writeString('=3 ');
if i>3 then writeString('>3 ');
if i>2 then writeString('>2 ');
if i>=3 then writeString('>=3 ');
if i>=4 then writeString('>=4 ');
if i<3 then writeString('<3 ');
if i<4 then writeString('<4 ');
if i<=3 then writeString('<=3 ');
if i<=2 then writeString('<=2 ');
if i<>2 then writeString('<>2 ');
if i<>3 then writeString('<>3 ');

pen(2);
locate(2,4);
writeString('Prueba de WHILE: 6+ ');
j := 5;
while j <= 10 do
begin
writeChar('+');
inc(j);
end;

pen(2);
locate(2,5);
writeString('Prueba de REPEAT: 5- ');
pen(3);
k := 10;
repeat
writeChar('-');
dec(k);
until k = 5;

locate(2,6);
pen(1);
writeString('For sin begin..end: ');
for j := 1 to 10 do
begin
writeChar('.');
end;

locate(2,7);
writeString('Prueba de GOTO');
goto escribirSinCambiarColor;
pen(2);
escribirSinCambiarColor:
writeString(' (Sin cambiar color)');

locate(2,8);
for i := 5 to 3 do
writeString('Esto no deberia escribirse');

end.


Con él, se ha podido descubrir varios fallos que tenía la versión 0.15, como:


  • Al añadir la comprobación basada en Tokens, había introducido un fallo en la declaración de constantes: debía terminar en coma, en vez de en punto y coma.

  • Todavía no estaba creado el código para reconocer correctamente las comparaciones , >=.

  • Cuando se insertaban elementos en la tabla de símbolos, no se comprobaba si ésta ya se había llenado.

  • En la segunda pasada, sólo se buscaba una coincidencia parcial de una cadena dentro de otra, lo que podría hacer que la etiqueta FIN_IF_10 se reemplazara por el valor de FIN_IF_1.



También ha servido para alguna pequeña mejora adicional como:


  • En el analizador sintáctico, la variable "orden" era global, lo que podría llegar a dar problemas cuando una función de análisis llame a otra distinta, como por ejemplo el caso de un IF o un FOR que contengan una sentencia compuesta.

  • Teníamos un "devolverLetra" que nos venía bien en el análisis sintáctico, para poder leer una letra más adelante (y así distinguir por ejemplo entre un : y un := ) pero no teníamos un "devolverToken", que es cómodo para situaciones como la comprobación de un ELSE.

  • La compatibilidad con Turbo Pascal 7 era limitada, por algún detalle como usar la función "upcase" para convertir toda una frase a mayúsculas, mientras que TP7 sólo permite usarla para un "char". Como primer acercamiento a una compatibilidad mejorada, he creado una función "mayusc" que convierte un string, usando "upcase" para cada letra.

  • El analizador léxico tenía una variable "depurando" que permitía ver más información de la habitual en pantalla; ahora esta posibilidad se encuentra también en el analizador sintático.



De paso, se podía haber hecho alguna otra mejora pendiente, como que el espacio para variables se reserve al final y no al principio, o que las cadenas de texto se almacenen al final del código, en vez de entre medias, para que sea más legible el código máquina resultante. Pero he preferido no hacerlo, para que todas las versiones tengan una cantidad de trabajo similar: una cantidad de trabajo mayor en un tiempo parecido puede suponer una gran cantidad de fallos... y no hay necesidad...


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/