22 junio 2008

Un compilador sencillo paso a paso (9 - Variables, primer acercamiento)

Ya habíamos preparado una primera tabla de símbolos, que nos permitía utilizar constantes en los programas. Ahora vamos a ampliar ligeramente la estructura para permitir el manejo básico de variables.



De cada símbolo estábamos guardando el nombre, el tipo (por ahora sólo byte) y el valor. En un principio, nos podría bastar para un manejo básico de variables a nuesto nivel. Aun así, hay otra diferencia entre el manejo de constantes y variables: una constante se declara a la vez que se le da un valor, pero una variable no: primero se declara y luego se le da un valor. Esto puede provocar que intentemos usar una variable que se ha declarado pero que todavía no tiene un valor. Hay tres formas de atacar este problema:



  • Ignorarlo, con lo cual se podrá acceder al valor de una variable a la que realmente no se ha dado valor, y que contendrá basura. Es lo que se suele hacer e los compiladores de C.


  • Dar un valor a las variables cuando se declaren. Por ejemplo, se podría dar por sentado que una variable numérica tendrá un valor inicial de cero. Es lo que se suele hacer en muchas versiones de Basic.

  • Anotar si se ha dado un valor a la variable o no, y mostrar un mensaje de error si se intenta usar una variable que no tiene valor. En el momento de compilar se puede hacer una primera estimación, y avisar de los posibles errores, como se hace en Java, pero la mayoría de comprobaciones "reales" habría que hacerlas en tiempo de ejecución.


Yo seguiré la primera aproximación: reservaré un espacio para cada variable, no le daré un valor incial, y no comprobaré si la variable tenía valor cuando se acceda a ella.



Habrá que modificar la tabla de símbolos ligeramente, porque de las variables no guardaremos su valor actual, sino la dirección de memoria en que se encuentran.


También habrá que ampliar ligeramente el analizador léxico: ahora, además del bloque CONST, podrá haber un bloque VAR, y ambos serán opcionales, por lo que esa parte del análisis sintáctico podría ser:




(* const o var, antes de begin *)
repeat

orden := upcase(obtenerIdentificador);
if orden = 'CONST' then
begin
analizarCONST;
end;

if orden = 'VAR' then
begin
analizarVAR;
end;

until orden = 'BEGIN';


El análisis sintáctico de la orden VAR sería muy parecido al de CONST:



procedure analizarVAR;
var
ident: string;
tipoVar: string;
terminador: char;
begin
ident := upcase(obtenerIdentificador);
{ Despues del identificador podra ir :tipo o ,var }
{ De momento, solo una variable }
leerSimbolo(':');
tipoVar := upcase(obtenerIdentificador);
if (tipoVar <> 'BYTE') then
errorFatal('Tipo incorrecto');
terminador := leerCualquierSimbolo;
if (terminador <> ';') then
errorFatal('Terminador incorrecto');
insertarSimboloVar(ident, 'b');
end;


Como se puede ver, ahí aparece una orden "errorFatal" que no existía antes. Es una pequeña mejora al análisis léxico: llevar cuenta de la línea y la columna en que estamos, para poder dar mensajes de error un poco más detallados. Reemplazará a los WRITELN con mensajes de error seguidos de HALT que estábamos usando hasta ahora :




(* Muestra un mensaje de error y abandona el programa *)
procedure errorFatal(mensaje: string);
begin
write('Linea: ',lineaActual,'. Pos: ',posicionLineaActual+1,'. ');
writeln(mensaje);
halt;
end;



La parte complicada del manejo de variables no es ésta, sino la que está por llegar. Habrá que dar valores a variables, que se guardarán en la posición de memoria que le hayamos reservado. Se deberá poder leer el valor de una variable. Se deberá poder hacer operaciones simples, como el incremento (INC) y el decremento (DEC). Eso lo haremos en el próximo acercamiento. Otras operaciones más complejas, como las aritméticas y las comparaciones tardarán un poco más en llegar...





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/