08 abril 2008

Un compilador sencillo paso a paso (7 - Primera tabla de símbolos)

Los programas que reconoce nuestro compilador pueden tener instrucciones con o sin parámetros, pero hasta ahora esos parámetros sólo pueden ser números. En el "mundo real" esto no es así, es muy frecuente necesitar trabajar con valores cambiantes, que se almacenarán en variables.

Como primer acercamiento, vamos a hacer que nuestro compilador sea capaz de reconocer constantes, y en la próxima entrega ampliaremos para que también sean variables.

Existen distintos tipos de datos. Como nuestra máquina de destino tiene un procesador de 8 bits, por ahora usaremos el más sencillo para nosotros: un tipo de datos Byte, que ocupe 1 byte en memoria y pueda almacenar un número entero con un valor entre 0 y 255. Es algo limitado, pero que nos permitirá hacer proyectos pequeños.

De cada constante necesitaremos varios datos: el nombre, el tipo de datos (por ahora sólo "byte") y su valor. Más adelante necesitaremos otros datos parecidos de cada variable (pero no guardaremos su valor actual, sino la dirección de memoria en que éste se encuentra) y de cada función.

Permitiremos que se pueda declarar varias constantes, separadas por comas. Todas tendrán la forma "nombre = valor", comenzarán con la palabra "const" y terminarán con un punto y coma, de modo que un program sencillo podría ser así:


program ej;
const linea = 10, columnaA = 10, columnaB = 1;
begin
cpcMode(0);
pen(2);
paper(1);
locate(columnaA, linea);
writeChar( 'a' );
pen(4);
paper(3);
locate ( columnaB , linea );
writeChar('c');
end.


Cuando el analizador sintáctico encuentre la palabra "const", deberá guardar información sobre la constante que se le indique (o las constantes, si son varias). Para ello, dentro del generador de código crearemos una función "insertarSimboloConst", que recibirá dos parámetros: el nombre y el valor, y una segunda función "leerSimboloConst", que recibirá el nombre de la constante y devolverá su valor. Nuestra tabla de símbolos será rudimentaria: para no perder tiempo en crear estructuras de datos más eficientes, será simplemente un "array" de registros. Por tanto, para insertar un símbolo, recorreremos este array, añadiendo al final, o dando un mensaje de error si el símbolo está repetido:


(* Guardar una constante en la tabla de símbolos *)
procedure insertarSimboloConst(nombreConst: string; valorConst: string);
var
i: integer;
begin
nombreConst := upcase(nombreConst);
(* Primero comprobamos si el simbolo ya existe *)
for i:=1 to numSimbolos do
if listaSimbolos[i].nombre = nombreConst then
begin
writeln('Constante repetida!');
halt;
end;
(* Si no existe, lo insertamos *)
numSimbolos := numSimbolos + 1;
listaSimbolos[numSimbolos].nombre := nombreConst;
listaSimbolos[numSimbolos].tipoDato := tBYTE;
listaSimbolos[numSimbolos].valorDir := valorConst;
end;



Para leer un símbolo de nuestra tabla de símbolos sencilla, recorremos el array, y damos un mensaje en caso de que no exista:


(* Leer una constante de la tabla de símbolos *)
function leerSimboloConst(nombreConst: string): byte;
var
encontrado: boolean;
i: integer;
x: byte;
codError: integer;
begin
nombreConst := upcase(nombreConst);
encontrado := false;
(* Primero comprobamos si el simbolo ya existe *)
for i:=1 to numSimbolos do
begin
if listaSimbolos[i].nombre = nombreConst then
begin
val(listaSimbolos[i].valorDir, x, codError);
leerSimboloConst := x;
encontrado := true;
end;
end;
(* Si no existe, mensaje de error *)
if encontrado = false then
begin
writeln('Constante inexistente!');
halt;
end;
end;



Ahora deberíamos modificar ligeramente las funciones que antes esperaban un parámetro numérico, como CpcMode, para que esta vez reciban un identificador o un número, si se trata de un identificador, deberemos buscar su valor en la tabla de símbolos. Lo podríamos hacer así:


procedure analizarCPCMODE;
begin
leerSimbolo('(');

cadenaTemp := obtenerEnteroOIdentificador;
if cadenaTemp[1] in ['a'..'z'] then
x := leerSimboloConst(cadenaTemp)
else
val(cadenaTemp,x,codError);

leerSimbolo(')');
leerSimbolo(';');
genCPCMODE(x);
end;


Con pocos cambios similares a ese, conseguiríamos que nuestro mini-compilador reconozca tanto valores numéricos como constantes.

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/