27 marzo 2008

Un compilador sencillo paso a paso (4)

Vamos a por el cuarto acercamiento. Hemos hecho que la entrada sea un poco más flexible, con un primer analizador léxico. Ahora le toca el turno a la salida: la generación de código.

Si hacemos que el generador de código sea un módulo independiente (lo elegante sería que fuera un "objeto"; para nosotros será simplemente unas cuantas funciones recopiladas en una misma "unit"), conseguiremos varias ventajas:

  • Más legibilidad de lo que podríamos considerar el "código fuente principal".
  • Más facilidad para reemplazar esa "unit" por otra cuando queramos generar código para otra máquina de destino distinta.
Aun así, nuestra aproximación es pobre:
  • En un compilador "real" se suele generar primero un código intermedio, parecido al ensamblador (en ocasiones puede ser algo más "sofisticado", como un árbol sintáctico, que a nosotros ahora nos pilla bastante lejos).
  • A este código se le suele echar un primer vistazo (automático) para intentar optimizar las cosas que la generación de código ha hecho de forma mejorable.
  • A continuación se genera el código máquina.
  • Finalmente, se suele intentar optimizar también ese código máquina.

Claramente, nosotros no hacemos nada de eso (al menos por ahora): nos limitamos a generar código máquina en una sola pasada.

Conociendo nuestras limitaciones, buscamos que nuestro "código principal" (lo que dentro de poco llamaremos el analizador sintáctico) sea más simple y legible que antes, así:


program cpcPaChi;

(* Chi - Un compilador de Pascal chiquitito para CPC
Por Nacho Cabanes - Version 0.03

Versiones hasta la fecha:

Num. Fecha Cambios
---------------------------------------------------
0.04 27-Mar-2008 Primera version con generador de codigo
independiente (en la unit "uPaChiG")

0.03 25-Mar-2008 Creado un analizador lexico independiente
(en la unit "uPaChiL")

0.02 23-Mar-2008 Admite CLS, LOCATE, WRITECHAR,
que lee desde fichero. Solo permite
una orden en cada linea, con formato fijo

0.01 21-Mar-2008 Preliminar: solo acepta CLS por teclado
y genera el codigo correspondiente

*)

uses
upachil, (* Analizador lexico *)
upachig; (* Generador de codigo *)

var
nombreOrigen, nombreDestino: string;
orden: string;

(* Analiza las opciones que teclee el usuario. Por ahora,
solo el nombre del fichero a compilar *)
procedure analizarOpciones;
begin
if paramcount >= 1 then
nombreOrigen := paramstr(1)
else
begin
writeln('No se ha indicado el nombre del fichero');
halt;
end;
abrirFicheroEntrada( nombreOrigen );
abrirFicheroDestino( 'salida.bas' );
end;


(* Genera el codigo de destino: codigo maquina de Amstrad CPC
dentro de un cargador en Basic *)
procedure analizarUnaOrden;
var
x, y: byte;
letra: char;
codError: integer;
begin
orden := upcase(obtenerIdentificador);
if orden = 'CLS' then
begin
genCLS;
end
else
if orden = 'WRITECHAR' then
begin
leerSimbolo('(');
leerSimbolo('''');
letra := obtenerLetra;
leerSimbolo('''');
leerSimbolo(')');
genWRITECHAR(letra);
end
else
if orden = 'LOCATE' then
begin
leerSimbolo('(');
val(obtenerEntero,x,codError);
leerSimbolo(',');
val(obtenerEntero,y,codError);
leerSimbolo(')');
genLOCATE(x,y);
end
else
if orden = '' then
begin
(* orden vacia *)
end
else
begin
writeln('Error: orden no reconocida. Se esperaba: CLS, LOCATE o WRITECHAR');
(*halt;*)
end;

end;


(* Cuerpo del programa *)
begin
writeln('Compilando...');
analizarOpciones;
while not finFicheroEntrada do
begin
analizarUnaOrden;
end;
generarCodigoFinal;
cerrarFicheroEntrada;
cerrarFicheroDestino;
end.


Esto lo conseguiríamos con una analizador sintáctico como éste:


unit upachig;

(* cpcPaChi - Un compilador de Pascal chiquitito para CPC
Por Nacho Cabanes

uPachiG - unidad "CodeGen" (generador de codigo)

Versiones hasta la fecha:

Num. Fecha Cambios
---------------------------------------------------
0.04 27-Mar-2008 Primera version del generador de codigo:
genera codigo para CLS, LOCATE, WRITECHAR
y el codigo final del cargador BASIC
para un codigo maquina que estara dentro
de ordenes DATA.
*)


interface

function abrirFicheroDestino(nombre: string): boolean;
procedure cerrarFicheroDestino;

procedure genCLS;
procedure genLOCATE(x,y: byte);
procedure genWRITECHAR(letra: char);

procedure generarCodigoFinal;


implementation

const
lineaActual: integer = 10;
longitudTotal: integer = 0;


var
ficheroDestino: text;


(* Abre el fichero de entrada. Devuelve FALSE si no se
ha podido *)
function abrirFicheroDestino(nombre: string): boolean;
begin
assign( ficheroDestino, nombre );
{$I-}
rewrite( ficheroDestino );
{$I+}

if ioResult <> 0 then
begin
abrirFicheroDestino := false;
end;
end;


(* Generar codigo para CLS *)
procedure genCLS;
begin
writeln( ficheroDestino, lineaActual,' DATA CD,6C,BB: '' CALL &BB6C - CLS' );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 3;
end;

(* Generar codigo para LOCATE *)
procedure genLOCATE(x,y: byte);
begin
writeln( ficheroDestino, lineaActual,' DATA 3E,',
hexStr(x,2), ': '' LD A, ',x );
lineaActual := lineaActual + 10;
writeln( ficheroDestino, lineaActual,' DATA CD,6F,BB: '' CALL &BB6F - CURSOR COLUMN' );
lineaActual := lineaActual + 10;
writeln( ficheroDestino, lineaActual,' DATA 3E,',
hexStr(y,2), ': '' LD A, ',y );
lineaActual := lineaActual + 10;
writeln( ficheroDestino, lineaActual,' DATA CD,72,BB: '' CALL &BB72 - CURSOR ROW' );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 10;
end;


(* Generar codigo para WRITECHAR *)
procedure genWRITECHAR(letra: char);
begin
writeln( ficheroDestino, lineaActual,' DATA 3E,',
hexStr(ord(letra),2), ': '' LD A, "',letra, '"' );
lineaActual := lineaActual + 10;
writeln( ficheroDestino, lineaActual,' DATA CD,5A,BB: '' CALL &BB5A - WRITECHAR' );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 5;
end;

(* Cerrar fichero de destino (al final de la generacion) *)
procedure cerrarFicheroDestino;
begin
close(ficheroDestino);
end;


(* Genera el codigo de destino final: cargador Basic *)
procedure generarCodigoFinal;
begin
append( ficheroDestino );
writeln( ficheroDestino, lineaActual,' DATA C9: '' RET' );
writeln( ficheroDestino, lineaActual+10,' longitud = ',longitudTotal );
writeln( ficheroDestino, lineaActual+20,' MEMORY 39999' );
writeln( ficheroDestino, lineaActual+30,' FOR n=40000 TO 40000+longitud' );
writeln( ficheroDestino, lineaActual+40,' READ a$:POKE n,VAL("&"+a$)' );
writeln( ficheroDestino, lineaActual+50,' NEXT' );
writeln( ficheroDestino, lineaActual+60,' CALL 40000' );
end;


begin
end.


Por cierto... leer código fuente en un Blog no especialmente cómodo. Si quieres leer esta información con sintaxis coloreada en colores, o echar un vistazo más detenido al fuente, o descargarlo a partir de un fichero ZIP, puedes mirar la página del proyecto en Google Code:

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