28 agosto 2008

Un compilador sencillo paso a paso (15 - Pequeñas mejoras)

Nuestro analizador léxico tiene todavía varias carencias. Posiblemente una de las más importantes es que los identificadores pueden estar formados por varias letras, pero los operadores no, por lo que todavía no podemos reconocer comparaciones como "<=".

Para solucionarlo, tenemos dos alternativas. La rápida pero mala es permitir que los "símbolos" estén formados por más de un carácter. La correcta es empezar a hablar (por fin) de "tokens". Así, tanto "if" como "+" o "<=" o la variable "x" serían tokens, cada uno de ellos con un "valor" o "lexema" y también con un "tipo" (junto con otros datos que nos pueden ser útiles, como la fila y columna en que se encuentra). Esta estructura estaba preparada desde la versión 0.07, pero no la habíamos utilizado hasta ahora.

Además aprovecharemos para corregir las asignaciones: los símbolos ":=" podían estar separados, por lo que hasta ahora ": =" se habría interpretado como una asignación válida. Nos vendrá bien usar, además de la función "leerCaracter" otra "devolverCaracter" que será necesaria cuando leamos de más al comprobar si al ":" le sigue un "=".

Además, orientado a la máquina de destino añadiremos una orden cpcInk, que permita jugar con la paleta de colores, redefinir color haciendo cpcInk(1,WHITE); o bien cpcInk(1,BLUE, RED); (este último formato parpadearía entre dos colores). Estas constantes para los colores estarán definidas también en el compilador, para que el código sea más legible. También añadiremos una orden WriteString, que escriba una cadena (prefijada) en vez de sólo una letra.

Ahora podremos hacer cosas como:

program ej15; { Ejemplo 15 de CpcPaChi }

var
i, j, k: byte;

procedure inicializa;
begin
cpcMode (1);
cpcInk(0,BLACK);
cpcInk(1,WHITE);
cpcInk(2,BRIGHTYELLOW);
cpcInk(3,PALEYELLOW);
paper(0);
end;

procedure lineaHorizontal;
begin
for i := 1 to 10 do
writeChar('*') ;
end;

(* Vamos a dibujar un cuadrado *)
begin

inicializa;
locate(2,2);
writeString('Alla vamos...');

locate(10,3);
pen(1);
lineaHorizontal;

pen(2);
for j := 4 to 7 do
begin
locate(10,j);
writeChar('*');
for k := 1 to 8 do
writeChar(' ');
writeChar('*');
end;

locate(10,8);
pen(3);
lineaHorizontal;

locate(20,20);
writeString('Hemos terminado!');
pen(1);
end.


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/

23 agosto 2008

Un compilador sencillo paso a paso (14 - Acercamiento a los procedimientos)

En los lenguajes estructurados como Pascal se pueden definir procedimientos, que "encapsulan" una serie de pasos, y funciones, que además devuelven un valor. Nuestro compilador aún tiene muchas carencias (no se pueden declarar dos variables a la vez, no existen distintos tipos de datos, faltan muchas órdenes por incluir...) y más de un error (por ejemplo, no es capaz de reconocer operadores formados por dos símbolos, como <=), pero aun así vamos a acercarnos un poco a cómo se podría generar el código para los procedimientos, y en el próximo paso será cuando vayamos limando asperezas para que nuestro compilador, además de hacer un poco de todo, funcione un poco mejor...

En una primera toma de contacto, queremos que nuestro compilador sea capaz de generar el código para procedimientos muy básicos: sin parámetros y sin variables locales (y, al no tratarse de funciones, tampoco devolverán ningún valor).

Pretendemos que sea capaz de traducir correctamente fragmentos como

procedure inicializa;
begin
cpcMode(1);
paper(0);
end;


Que luego se usarían desde el cuerpo del programa:

begin
inicializa;
locate(10,3);
...


Esto supone una serie de problemas:

  • El primer código que encontramos al analizar el programa no será siempre realmente el cuerpo del programa, sino que puede tratarse de un procedimiento que será llamado más adelante... o quizá incluso nunca.

  • Cada procedimiento podrá ser llamado desde el cuerpo del programa o desde otro procedimiento, y cuando termine su ejecución deberá volver al punto siguiente del cuerpo del programa (o del otro procedimiento).



Podemos intentar resolverlo así:

  • Podríamos incluir el código de los procedimientos al final, después del código del cuerpo del programa, pero si queremos hacer todo el análisis en una pasada no es la mejor solución. La alternativa para poder ir convirtiendo a medida que se va leyendo es generar el código de los procedimientos según se encuentren, es decir, antes del programa. Por eso, como es posible que lo primero que encontremos no sea lo primero que debemos ejecutar, el programa debería comenzar con un salto al comienzo del cuerpo (que aún no sabemos dónde será): JP INICIO_PROGRAMA.

  • Cada procedimiento terminará con un RET, para volver al punto desde el que se llamó, y tendrá una dirección de comienzo, a la que llamaremos con un CALL.



Así, el programa convertido a ensamblador tendría una apariencia similar a:

JP INICIO_PROGRAMA
INICIO_INICIALIZA
...
RET
INICIO_LINEA_HORIZONTAL
...
RET
INICIO_PROGRAMA
...


La situación se complica si queremos permitir variables locales y/o parámetros. No lo vamos a hacer todavía, pero sí a ver las nociones básicas, para aplicarlas más adelante:

  • El hecho de que haya variables locales, y de que alguna de ellas pueda llamarse igual que una variable global, se puede solucionar añadiendo información adicional en la tabla de símbolos: el ámbito al que pertenece una variable. Por ejemplo, para una variable "i" en el procedimiento "inicializa" podría haber otro campo que indique el procedimiento al que pertenece, o podríamos simplemente cambiar su nombre por "i_inicializa" durante el análisis, pero eso fallaría si el procedimiento (o función) es recursivo. También deberíamos plantearnos declarar la variable cuando se entra al procedimiento y eliminar la declaración al salir de él, para no desperdiciar espacio que quizá no se esté usando.

  • Sobre los parámetros, la forma habitual de trabajar es preparar un "registro de activación", que contiene detalles sobre los parámetros, y quizá incluso sobre datos locales e "información administrativa", como la dirección a la que se debe volver cuando se termine de analizar el procedimiento. De momento todo ello son datos que nosotros no vamos a usar.



De paso, aprovecharemos para hacer alguna pequeña mejora: que se puedan declarar varias variables del mismo tipo a la vez, algo que todavía no permitía nuestro analizador:

var
i, j, k: byte;

20 agosto 2008

Un compilador sencillo paso a paso (13 - Estructuras repetitivas)

Si sabemos cómo hacer saltos, y cómo comprobaciones del tipo "if..then..else", crear construcciones como "while" y como "repeat..until" debería ser muy fácil. Tampoco debería ser muy complejo crear secuencias "for". Vamos con ello...

Comencemos por "while". Un bloque como

while condicion do
begin
bloque1
end
restoDePrograma


Se podría reescribir como

inicioWhile:
if not condicion then goto inicioRestoDePrograma;
bloque1
goto inicioWhile;
inicioRestoDePrograma:
restoDePrograma


Que, al igual que ocurría con "if", ya tiene una traducción casi directa a ensamblador (y, por tanto, a código máquina).


El caso de "repeat..until" es aún más sencillo. El código fuente sería

repeat
bloque1
until condicion;
restoDePrograma


Se podría reescribir en forma muy cercana al ensamblador como

inicioRepeat:
bloque1
if not condicion then goto inicioRepeat;
restoDePrograma


Mientras que el bucle "for" es apenas ligeramente más complicado:

for variable := valorInicial to valorFinal do
begin
bloque1
end
restoDePrograma


Quedaría como

variable := valorInicial;
inicioFor:
if variable > valorFinal then goto inicioRestoDePrograma;
bloque1
inc(variable)
goto inicioFor;
inicioRestoDePrograma:
restoDePrograma


(ignoramos por ahora el caso de usar "downto" en vez de "to").

Aun así, hay cosas que mejorar (en una versión posterior). Unas son fallos claros de la versión actual y otras son mejoras necesarias. La primera y más evidente es que nuestro analizador léxico no reconoce operadores de formados por dos letras: ni "<=", ni ">=", ni "<>". Tampoco se crea correctamente el código objetivo para estas comparaciones. Por supuesto, tampoco permite AND, OR y NOT para formar condiciones más complejas. Tampoco sabemos analizar expresiones matemáticas más elaboradas, de modo que no podemos hacer cosas como "if a > x+1 then ..."

Todo irá llegando... ;-)

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/

18 agosto 2008

Un compilador sencillo paso a paso (12 - Condiciones simples)

Ahora que sabemos hacer saltos, podemos hacer comprobaciones del tipo if..then..else

La idea es que un bloque como

if condicion then
begin
bloque1
end
else
begin
bloque2
end
restoDePrograma


Se podría reescribir como

if not condicion then goto inicioBloque2;
begin
bloque1
goto inicioRestoDePrograma;
end;
inicioBloque2
begin
bloque2
end
inicioRestoDePrograma:
restoDePrograma


Y esa construcción ya tiene una traducción casi directa a ensamblador (y, por tanto, a código máquina).


En cuanto a las condiciones, nos centraremos por ahora en condiciones muy sencillas, del estilo de "if x > 2 then ...".

La forma en que lo haremos en código máquina de Z80 será la siguiente:


  • El primer valor a comparar lo guardaremos en el registro A.

  • El segundo valor lo guardaremos en el registro B.

  • Compararemos los valores guardados en ambos registros, usando la orden CP (que realmente hace una resta entre ambos valores).

  • Usaremos el "flag" adecuado para cada tipo de comparación: para "if a = b" usaríamos "JP Z" (saltar si se activa el flag Z porque el resultado de la última operación sea cero); para "if a <> b" usaríamos "JP P" (saltar si resultado positivo).



También es necesario algún otro cambio pequeño en el fuente, para permitir bloques begin...end, pero eso es muy sencillo...

13 agosto 2008

Colorear sintaxis en la web

Para los que publicamos fuentes de programas en la web, es interesante poder hacerlo con la sintaxis realzada en colores, de forma que resulte más legible para los usuarios de nuestro sitio web.

Hay varias formas de conseguirlo:

- Para los más atrevidos y/o los que publican pocos fuentes, está la opción de emplear un editor de texto que sea capaz de exportar a HTML con la sintaxis en colores, como es el caso de Notepad++, de PsPad y de Scite.

- Para los que tengan un sitio web en PHP y publiquen fuentes en PHP, se puede usar las propias tutinas de coloreado existentes en PHP: para una orden highlight_string(''); o para un fichero highlight_file("ejemplo.php")

- Para los que publiquen fuentes en lenguajes "habituales", como C++, Python o SQL, pueden probar a usar el paquete Text_Highlighter de la libería PEAR para PHP. Hay más detalles (en inglés) y algún ejemplo de su uso en: http://www.sitepoint.com/print/highlight-source-code-php

- Para los que necesiten algo todavía más versátil, porque usen lenguajes "hoy menos habituales", como Pascal o Basic, o porque quieran ser capaces de crearse sus propios esquemas de coloreado para otros lenguajes, puede merecer la pena echar un vistazo a Geshi:

http://qbnz.com/highlighter/index.php

Un ejemplo básico de su uso sería:

// Include the GeSHi library
include('geshi.php');

// Fuente (una cadena - incluso varias líneas), lenguaje y carpeta de datos de lenguajes
$fuente = 'echo "hola, mundo!";
// Comentario en segunda linea!!!!';
$lenguaje = 'php';
$carpeta = 'geshi/';

$geshi = new GeSHi($fuente, $lenguaje, $carpeta);

// y volcar el resultado
echo $geshi->parse_code();


Si queremos mostrar todo un fichero en vez de unas pocas líneas, usaríamos "file_get_contents":

$source = file_get_contents('ej001.pas');;


No parece difícil, ¿verdad?

Free Pascal 2.2.2 liberado

Hace 2 días, el 11 de Agosto, se liberó la versión 2.2.2 de Free Pascal, el compilador de Pascal multiplataforma.

La mayoría de cambios en esta versión son de corrección de errores, pero también se ha reemplazado por código nuevo alguno que podía ser sospechoso de plagio de Borland y se han restructurado algunos paquetes (especialmente en la versión para MacOS y en la de Windows CE).

Esta versión está disponible para Windows 32 bits, Windows 64 bits, Windows CE, Mac OS X, Linux (bajo procesadores i386, amd64, arm, sparc, powerpc, powerpc64), FreeBSD, DOS y OS/2.

Como siempre, más información (en inglés) y descargas en

http://www.freepascal.org/

o su alternativa en español (menos detallada), para quien no hable inglés:

http://www.freepascal.es/

y más detalles (en inglés) sobre las novedades en

http://wiki.freepascal.org/User_Changes_2.2.2

11 agosto 2008

Un compilador sencillo paso a paso (11 - Saltos incondicionales)

Las estructuras condicionales, como "if", y las repetitivas como "while" y "for", cuando se conviertan a código máquina, necesitarán realizar saltos a partes anteriores o posteriores del programa. Por eso, como forma de acercarnos a esa problemática, vamos a crear saltos incondicionales: la peligrosa orden "goto".

Eso supone que también podamos definir etiquetas, para indicar a qué lugar queremos saltar. En Pascal, quedaría algo como:

label destino;
begin
...
destino:
...
goto destino;
...
end.


La forma más sencilla (aunque poco eficiente) de convertirlo a código máquina es usar saltos a direcciones absolutas de memoria. Por ejemplo, un salto atrás en ensamblador quedaría así:


...
.destino
...
jp destino
...


La traducción a ensamblador es prácticamente inmediata. Pero nosotros no queremos compilar a ensamblador, sino directamente a código máquina, de modo que no podemos emplear etiquetas, sino que debemos indicar a qué posicion de memoria queremos saltar. Si se trata de una posición de memoria anterior, no es díficil: como ya la conocemos, podríamos construir sin problemas la orden de salto:


30000: ...
...
C3 30 75; jp 30000
...


Pero si se trata de un salto hacia un punto posterior, estamos atados de manos: todavía no sabemos de qué dirección de memoria se trata, lo que nos obliga a "retroceder" cuando ya la conozcamos: en ensamblador sería


...
jp destino
...
.destino
...


Que en código máquina se convertiría en:


...
C3 ?? ?? ; jp ????
...


Esas "interrogaciones" representan lo que todavía no sabemos, huecos que tendremos que rellenar más adelante, y que nos obligarán a dar una segunda pasada (aunque sencilla). Como estamos volcando a fichero a medida que compilamos, tenemos dos opciones


  • Conservar todo el programa en memoria, para poder dar la segunda
    pasada antes de volcar a disco.

  • Tras terminar la primera pasada, volver a abrir el fichero generado
    para dar la segunda pasada, volcando el resultado a un nuevo fichero.



La primera opción sería la más eficiente en cuanto a velocidad, pero necesita más espacio en memoria, y partimos de la idea de que quizá algún día el compilador llegue a poder compilarse a sí mismo y funcionar desde la máquina de destino, un equipo con unos 64 Kb de memoria. Por eso, consideraremos la memoria como un bien muy valioso, y seguiremos volcando a disco, para dar una segunda pasada sobre ese fichero resultante en disco.

Así, el resultado de la primera salida para esa instrucción de
salto podría ser algo como:


...
DATA,C3,destinoLOW,destinoHIGH ; jp destino
...


(Lo de descomponer "destino" en "destinoLOW" y "destinoHIGH" es porque en la máquina que estamos tomando como destino se sigue el criterio habitual de codificar los datos de más de un byte como el byte menos significativo seguido por el más significativo -"little endian").


La segunda pasada podría buscar cada uno de los identificadores de la tabla de símbolos (como "destino") para ver si aparecen descompuestos en "destinoLOW" y "destinoHIGH" y reemplazarlos por sus valores.


Ya que vamos a dar dos pasadas, podemos optimizar algo que antes habíamos hecho de una forma sencilla pero no demasiado correcta:podemos dejar las variables a continuación del código, en vez de colocarlas al principio y separadas del código por un espacio arbitrario. Ahora otros fragmentos de nuestro código serán cosas como:


...
DATA,3E,[xBYTE] ; LD A, variableX
...


Y el espacio para esa "variableX" se reservaría al final, de la primera pasada, tras el código del programa, y el valor exacto de la dirección se sustituiría en la segunda pasada.



Aun así, esa mejora de momento la aplazamos para algo más adelante...

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/

06 agosto 2008

Un compilador sencillo paso a paso (10 - Variables utilizables)

Tras un paréntesis forzoso durante julio por exceso de trabajo y de compromisos familiares, vamos a retomar el proyecto, a ver si hay suerte y durante agosto queda terminado un compilador razonablemente utilizable...

En el acercamiento anterior habíamos comentado alguna de las ideas básicas del manejo de variables, y habíamos un primer acercamiento al analizador que reconozca la declaración de variables, pero todavía no sabíamos manipularlas. Eso es lo que haremos ahora.

Tendremos que ser capaces de hacer cosas como:

  • Dar un valor a una variable: a := 1

  • Cambiar el valor de una variable. La primera forma que haremos, porque no supone analizar expresiones aritméticas, podría ser la operación "incremento": inc(a).

  • Usar el valor de una variable, con operaciones como cpcMode(a).



Como las variables se almacenan en operaciones de memoria, todas esas operaciones supondrán leer el valor de la memoria, y, a veces, modificarlo y volverlo a guardar. En concreto, si suponemos que la variable "a" está en la posición de memoria 10.000, esas operaciones se podrían convertir a las siguientes secuencias en ensamblador:

a := 1

ld a, 1 ; Cargar el valor 1 en el acumulador
ld (10000), a ; Guardar en la posición 10.000 el valor del acumulador


inc(a)
ld a, (10000) ; Guardar en el acumulador el valor guardado en la posición 10.000
inc a ; Incrementar el valor del acumulador
ld (10000), a ; Guardar en la posición 10.000 el valor del acumulador


cpcMode(a)
ld a, (10000) ; Guardar en el acumulador el valor guardado en la posición 10.000
call &bc0e ; Cambiar modo de pantalla


Esto supone varios cambios en el fuente: para instrucciones como INC bastará con añadir nuevas rutinas de generación de código, pero para órdenes existentes como cpcMode ahora deberemos comprobar si el parámetro es una constante (y en ese caso haríamos cosas como LD A,1) o si es una variable (y entonces tomaremos el valor desde una posición de memoria: LD A, (10000) ).

Pero también hay otro cambio grande: ¿cómo asignamos las direcciones de memoria para esas variables?  ¿Y dónde dejamos el espacio para ellas?

Hay varias soluciones:

  • La primera solución, la más eficiente en cuanto a espacio, sería dejar el espacio para las variables al final del programa. Pero esta solución tiene un problema: hasta que no terminamos de generar el programa, no sabemos cuánto ocupa, de modo que no hasta entonces no sabríamos las direcciones de las variables, por lo que deberíamos dar dos pasadas: en la primera pasada se genera todo el programa excepto las direcciones de las variables (se suele dejar indicado con una etiqueta, algo como "LD A, [variableX]"), y en la segunda pasada será cuando se escriban realmente las posiciones de memoria en lugar de estas etiquetas.

  • La segunda solución es dejar el espacio para variables al principio del programa. Ahora el inconveniente será parecido: como no sabemos cuantas variables hay en el programa, no tendremos claro en qué posición de memoria empezarán las órdenes... hasta que no demos una primera pasada. Además, existe un segundo riesgo: antes lanzábamos nuestro programa resultante con algo como CALL 40000, pero ahora la primera orden ya no está en esa posición, sino en otra, así que deberemos recordar en cual, para que el cargador sea adecuado (por ejemplo, CALL 40015).

  • Nosotros queremos esquivar (todavía) las dos pasadas, e intentar hacerlo todo en una pasada. Así que la aproximación que haremos es dar por sentado que "algo" ocupa una cierta cantidad de bytes como máximo. Por ejemplo, podríamos suponer que el programa empieza en la posición de memoria 30.000, y que no ocupa más de 10.000 bytes, por lo que las variables podrían estar en la posición de memoria 40.000. Como el tamaño de un programa es algo muy difícil de prever, y la cantidad de variables no lo es tanto, quizá fuera más razonable decir que las variables empiezan en la posición 30.000 (por ejemplo) y el programa en la 30.200. No es una aproximación fiable, pero sí es sencilla, y todavía buscamos sobre todo sencillez. Para evitar "despistes" en los que un CALL 30000 no accediera al programa sino al comienzo de los datos, lo que podría tener resultados imprevisibles, nos reservaremos los primeros 3 bytes de datos, no para variables, sino para almacenar una orden de salto a la posición 30200 (o a la que hayamos decidido que será la de comienzo del programa), o bien podemos obligarnos a rellenar con 0 (NOP) toda esa
    zona de variables inicialmente.



Vamos a ver los cambios que todo esto supondría al código del compilador:

En primer lugar, en la tabla de símbolos podremos guardar constantes y variables, luego podemos añadir un dato "constVar" que sea el que indique si un símbolo es una constante (cuyo valor podremos leer directamente en la tabla, pero no modificar) o una variable (para la que guardaremos la dirección de memoria, y el valor obtenido se deberá poder modificar). Por tanto, el registro de cada símbolo podría ser:


type simbolo =
record
nombre: string; (* Nombre del simbolo *)
constVar: integer; (* Si es CONST o VAR *)
tipoDato: integer; (* Tipo base: BYTE, etc *)
valorDir: string; (* Valor o direccion *)
end;


Para ese dato "constVar" que indica si es una constante o una variable, podemos definir dos constantes que nos ayuden a mantener la legibilidad del código:


const
tCONST = 1; (* Dato CONST en tabla de simbolos *)
tVAR = 2; (* Dato VAR en tabla de simbolos *)


Ahora la rutina de insertar una constante deberá tener ese dato en cuenta:


(* Guardar una constante en la tabla de simbolos *)
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
errorFatal('Constante repetida!');
(* Si no existe, lo insertamos *)
numSimbolos := numSimbolos + 1;
listaSimbolos[numSimbolos].nombre := nombreConst;
listaSimbolos[numSimbolos].constVar := tCONST;
listaSimbolos[numSimbolos].tipoDato := tBYTE;
listaSimbolos[numSimbolos].valorDir := valorConst;
end;


La rutina de insertar una variable será parecida, salvo porque no tenemos un valor, sino una dirección. Además, esta dirección no la deberíamos fijar nosotros a mano, sino que se debería ir recalculando a medida que guardemos variables. Así, podríamos delegar en una función auxiliar "proximaDireccionLibre", a la que además le podemos indicar el tamaño del dato, de forma que sea algo más eficiente cuando nuestras variables no sean sólo de tipo byte:


(* Guardar una variable en la tabla de simbolos *)
procedure insertarSimboloVar(nombreVar: string; tipoVar: char);
var
i: integer;
begin
nombreVar := upcase(nombreVar);
(* Primero comprobamos si el simbolo ya existe *)
for i:=1 to numSimbolos do
if listaSimbolos[i].nombre = nombreVar then
errorFatal('Identificador repetido!');
(* Si no existe, lo insertamos *)
numSimbolos := numSimbolos + 1;
listaSimbolos[numSimbolos].nombre := nombreVar;
if tipoVar = 'b' then
listaSimbolos[numSimbolos].tipoDato := tBYTE
else
begin
writeln('Tipo de datos desconocido!');
halt;
end;
listaSimbolos[numSimbolos].constVar := tVAR;
(* Direccion, en vez de valor *)
listaSimbolos[numSimbolos].valorDir := proximaDireccionLibre(tByte);
end;


Finalmente, ahora las funciones podrán tener como parámetros constantes
numéricas, constantes con valor, o variables, por lo que su análisis "previo" será algo más trabajoso que antes, así que podríamos crear una rutina específica que se encargue de ello. De este modo, rutinas como la de analizar la orden PEN, que antes era así:


procedure analizarPEN;
begin
leerSimbolo('(');

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

leerSimbolo(')');
leerSimbolo(';');
genPEN(x);
end;


Pasarán a ser simplemente así:


procedure analizarPEN;
begin
leerSimbolo('(');
genLecturaValor(obtenerEnteroOIdentificador);
leerSimbolo(')');
leerSimbolo(';');
genPEN(x);
end;


Y ese procedimiento encargado de generar la orden de lectura del valor según se trate de una constante numérica, una constante con nombre o una variable podría ser así:


procedure genLecturaValor(nombre: string);
var
valorByte: byte;
direccion: integer;
begin
if nombre[1] in ['0'..'9'] then
begin
(* Si es numero *)
val(nombre,valorByte,codError);
writeln( ficheroDestino, lineaActual,' DATA 3E,',
hexStr(valorByte,2), ': '' LD A, ',valorByte );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 2;
end
else
if leerSimboloTS(nombre) = tCONST then
(* Si es constante con nombre *)
begin
valorByte := leerSimboloConst(nombre);
writeln( ficheroDestino, lineaActual,' DATA 3E,',
hexStr(valorByte,2), ': '' LD A, ',valorByte, ' - Constante ',nombre );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 2;
end
else
(* Si es variable *)
begin
direccion := leerSimboloVar(nombre);
writeln( ficheroDestino, lineaActual,' DATA 3A,',
hexStr(direccion mod 256,2), ',',
hexStr(direccion div 256,2),
': '' LD A, (',direccion, ') - Variable ',nombre );
lineaActual := lineaActual + 10;
longitudTotal := longitudTotal + 3;
end;
end;



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/