Después
de los análisis Lexicos, sintácticos y semánticos, algunos compiladores generan
una representación intermedia explicita del programa fuente. Se puede
considerar esta representación intermedia como un programa para una maquina
abstracta. Esta representación intermedia debe tener dos propiedades
importantes, debe ser fácil de producir y fácil de traducir al programa objeto.
*El
código intermedió es particularmente utilizado cuando el objetivo de compilador
es producir código muy eficiente, ya que para hacerlo así se requiere una
cantidad importante del análisis de las propiedades del código objetivo, y esto
se facilita mediante el uso del código intermedio.
*El
código intermedio también puede ser útil al hacer que un compilador sea mas
fácilmente re dirigible: si el código intermedio es hasta cierto punto
independiente de la maquina objetivo, entonces genera código para una maquina
objetivo diferente solo requiere volver a escribir el traductor de código
intermedio a código objetivo, y por lo regular esto es mas fácil que volver a
escribir todo un generador de código.
*La
representación intermedia puede tener diversas formas, se trata una forma
intermedia llamada "CÓDIGO DE TRES DIRRECCIONES" y el "CÓDIGO
P"
*El
código de tres direcciones consiste en una secuencia de instrucciones, cada una
de las cuales tiene como máximo tres operadores.
temp1 :=
entareal(60)
temp2 := id3 * temp1
temp3 := id2 + temp2
id1 := temp3
Esta
representación intermedia tiene varias propiedades.
-
primera, cada instrucción de tres direcciones tiene a lo sumo un operador,
además de la asignación. Por tanto, cuando genera esas instrucciones, el
compilador tienes de decidir el orden en que deben efectuarse las operaciones;
la multiplicación precede a la adicción en el programa fuente.
-
segunda, el compilador debe generar un nombre temporal para guardar los valores
calculados por cada instrucción.
-
tercera, algunas instrucciones de "tres direcciones" tiene menos de
tres operadores, por ejemplo, la primera y la ultima instrucción.
EL CÓDIGO P
*El
código P comenzó como un código ensamblador objetivo estándar producido por
varios compiladores Pascal en la década de 1970 y principios de la de 1980. Fue
diseñado para código real para una maquina de pila hipotética la idea era hacer
que los compiladores de Pascal se transportaran fácilmente requiriendo solo que
se volviera a escribir el interprete de la maquina P para una plataforma, el
código P también a probado ser útil como código intermedio y sean utilizado
varias extensiones y modificaciones del mismo en diverso compiladores de código
nativo,
La
mayor parte para lenguaje tipo Pascal.
*Como
el código P fue diseñado para ser directamente ejecutable, contiene una descripción
implícita de un ambiente de ejecución particular que incluye tamaños de datos,
además de mucha información especifica para la maquina P, que debe conocer si
se desea que un programa de código P se comprensible. La maquina P esta
compuesta por una memoria de código, una memoria de datos no específica para
variables nombre das y una pila para datos temporales, junto como cualquiera
registro que sea necesario para mantener la pila y apoyar la ejecución.
COMPARACIÓN
*El
código P en muchos aspectos está más código de maquina real que al código de
tres direcciones. Las instrucciones en código P también requiere menos de tres
direcciones: tosas las instrucciones que hemos visto son instrucciones de
"una dirección" o "cero direcciones". Por otra parte, el
código P es menos compacto que el código de tres direcciones en términos de
números de instrucciones, y el código P no esta "auto contenido" en
el sentido que las instrucciones funciones implícitas en una pila (y las
localidades de pila implícitas son de hecho las direcciones
"perdidas"). La ventaja respecto a la pila es que contiene los
valores temporales necesarios en cada punto del código, y el compilador no
necesita asignar nombre a ninguno de ellos, como el código de tres direcciones.
Ejemplo
·
Se puede considerar esta una representación intermedia como un programa para una maquina
abstracta.
·
Traducción
de una proposición
CÓDIGO DE TRES DIRECCIONES
Las
reglas semánticas para generar código de tres direcciones a partir de
construcciones de lenguaje de programación comunes son similares a las reglas
para construir arboles sintácticos o para notación posfija.
El
código de tres direcciones es una secuencia de preposiciones de la forma
general
x
:= y op z
Donde
'x', 'y' y 'z' son nombres, constates o variables temporales generadas por el
compilador, op representa cualquier valor, como un operador
aritmético de punto fijo o flotante, o un operador lógico sobre datos con
valores booleanos. Obsérvese que no se permite ninguna expresión aritmética
compuesta, pues solo hay un operador en el lado derecho de una proposición. Por
tanto, una expresión del lenguaje fuente como x+y*z se puede
traducir en una secuencia
t1
:= y * z
t2
:= x + t1
Donde
t1 y t2 son nombres temporales generados por el compilador.
El
código de tres direcciones es una reprehensión linealizada de un árbol
sintáctico o un GDA en la que lo nombres explícitos corresponde a los nodos interiores
del grafo.
t1 :=
-c
t1 := -c
t2 := b *
t1
t2 := b*t1
t3 :=
-c
t5 := t2 + t2
t4 := b *
t3
a := t5
t5 := t2 +t4
a := t5
(a)
Código para el árbol sintáctico
(b) Código para el GDA
Las
proposiciones de tres direcciones son análogas al código ensamblador. Las
proposiciones pueden tener etiquetas simbólicas y existen proposiciones para el
flujo del control. Una etiqueta simbólica representa el índice de una
proposición de tres direcciones en la matriz que contiene código
intermedio. Los índices reales se pueden sustituir por las etiquetas ya sea
realizado una pasada independiente o mediante "relleno en retroceso".
La
notación gen(x ':=' y '+' z) en la figura (1) para
representar la proposición de tres direcciones x := y + z
Se
puede añadir proposiciones de flujo del control al lenguaje de asignaciones de
la figura (1) mediante producciones y reglas semánticas como las de las
proposicioneswhile de la figura (2).
1.)
Definición dirigida por la sintaxis para producir código de tres direcciones
para las asignaciones.
Producción
|
Reglas
semántica
|
S à id := E
|
S. Código
:= E. código || gen (id. lugar ':=' E. lugar)
|
E à E1 +E2
|
E. lugar
:= tempnuevo;
E. código
:= E1. código || E2. código || gen(E. lugar ':=' E1. lugar '+' E2.lugar)
|
E à E1 * E2
|
E. lugar
:= tempnuevo;
E. código
:= E1. código || E2. código || gen(E. lugar ':=' E1. lugar '*' E2.lugar)
|
E à -E1
|
E. lugar
:= tempnuevo;
E. código
:= E1. código || gen('E. lugar ':=' 'menosu' E1. lugar)
|
E à (E1)
|
E.
lugar := E1. lugar;
E. código
:= E1. código
|
E à id
|
E. lugar
:= id. lugar;
E.
código := ' '
|
2.) Reglas semánticas que genera código para una proposición while:
IMPLEMENTACIÓN DE CÓDIGO DE TRES DIRECCIONES
·
Cuádruplas.
·
Estructura con 4 campos: operador, operando1, operando2, resultado.
·
Triplas.
·
Estructura con 3 campos: operador, operando1, operando2. Los resultados
temporales se referecian por la posición en que fueron calculados.
EL CÓDIGO P
El
código P comenzó como un código ensamblador objetivo estándar producido por
varios compiladores Pascal en la década de 1970 y principios de la de 1980. la
descripción de diversas versiones de código P.
La
máquina P está compuesta por una memoria de código, una memoria de datos no
especificada para variables nombradas y una pila para datos temporales, junto
cualquier registro que sea necesario para mantener la pila y apoyar la
ejecución.
Como
primer ejemplo se considera la expresión:
·
La versión
de código
P para esta expresión
es la que se muestra en seguida:
ldc
2 ; carga la constante 2
lod
a ; carga el valor de la variable a
mpi
; multiplicación entera
lod
b ; carga el valor de la variable b
ldc
3 ; carga la constante 3
sbi
; sustracción o resta entera
adi
; adiciona de enteros
Esta instrucción se ven como si representaran las siguientes operaciones
en una maquina P.
En
primer lugar, ldc 2 inserta el valor 2 en la pila temporal.
Luego, lod a inserta el valor de la variable a en
la pila. Las instrucción mpi extrae estos dos valores de la
pila, los multiplica (en orden inverso) e inserta el resultado en la pial. Las
siguientes dos instrucciones (lod b y ldc 3) inserta valor de b y
la constante 3 en la pila (ahora tenemos tres valores en la
pila). Posteriormente, la instrucción sbi extrae los dos
valores superiores de la pila, resta el primero del segundo, e inserta el
resultado. Finalmente, la instrucción adi extrae los dos
valores restantes de la pila, los suma e inserta el resultado. El código final
con un solo valor en la pila, que representa el resultado del cálculo.
IMPLEMENTACIÓN DEL CÓDIGO P
Históricamente, el código P ha sido en su
mayor parte generado como un archivo de texto, pero las
descripciones anteriores de las implementaciones de estructura de datos
internas para el código de tres direcciones (cuádruples y triples)
también funcionara como una modificación propia para el código P.
GENERACION DEL CODIGO PARTIENDO DE LI. ALGORITMOS:
Se sabe que entre el parse y el lenguaje objeto
resultado de la compilación, habrá una de estas dos cosas o ambas.
Un lenguaje intermedio LI.
Una generación de código.
Se supone la existencia de ambos componentes, nos
hace falta ahora el regresar el código objeto para la maquina objeto deseada,
que en el caso normal de no tratarse de un compilador cruzado, es el mismo
código con el que está escrito el compilador.
Como ejemplo de las posibilidades existentes se
mencionan las de la figura 5 para el lenguaje pascal.
Se emplea in código P para un maquina hipotética
que opera con una pila. Este compilador es muy portable de una maquina a otra.
Es el método usado en el USCD PASCAL que al estar todo escrito en el código de
la maquina ficticia P, sólo se precisa tener un pequeño interprete distinto
para cada maquina objeto dada.
Pero la hipotética maquina a pila, ya nos es tan
hipotética puesto que ahora existe ya como microprocesador, con lo que el
código P se ejecuta directamente.
También es como el primer caso, pero en vez de
emplear un interprete, se traduce con un ensamblador para la maquina objeto
dada.
El compilador puede dar directamente un módulo
cargable para la maquina objeto en cuestión.
El compilador suministra un objeto responsable que
se puede montar con otros objetos.
Para dar concreción vamos a definir el conjunto de
instrucciones de un ordenador sencillo que tenga sólo un acumulador A y un
registro índice X. Una instrucción simbólica con un ensamblador para esta
maquina tendría hasta 4 partes separadas entre si por blancos:
-Una parte opcional, la etiqueta.
-El código de operación.
-El operando.
-Comentario opcional.
La forma de direccionar la memoria son las
siguientes:
DIRECTA: Se
toma como operando el contenido en memoria del campo de dirección.
INDIRECTA: Como
antes pero, el contenido de memoria se considera a su vez como dirección del
dato.
INMEDIATA: El
valor de la dirección lo tomo inmediatamente como operando.
Y salvo en el caso del direccionamiento inmediato,
las direcciones pueden ser:Absolutas o reales, Relativas a la instrucción
actual, Indexadas con un registro.
TÉCNICAS BÁSICAS DE GENERACIÓN DE CÓDIGO
La
generación de código intermedio(o generación de código objetivo directa sin
código intermedio), en realidad, si el código generado se ve como un atributo
de cadena, entonces este código se convierte en un atributo sintetizado.
Para
ver como el código de tres direcciones, o bien, el código P se puede definir
como un atributo sintetizado, considere la siguiente gramática que representa
un pequeño subconjunto de expresiones en C:
exp à id = exp
| aexp
aexp à aexp + factor
| factor
factorà (exp)| num | id
El
código P considerando el caso de la generación de código P en primer lugar, ya
que la gramática con atributos es más simple debido a que no es necesario
generar nombres para elementos temporales.
Por
ejemplo, la expresión (x=x+3)+4 tiene ese atributo
lda
x
lod
x
ldc
3
adi
stn
ldc
4
adi
Gramática con atributos para código de tres
direcciones de cadena sintetizada
Regla
Gramatical
|
Reglas
Semánticas
|
exp à id = exp2
|
exp1.pcode ="lda" ||id.strval++
exp2.pcode++"stn"
|
exp à aexp
|
exp. pcode
=aexp.pcode
|
aexp1à aexp2 + factor
|
aexp1.
pcode = aexp2.pcode ++ factor. pcode ++ "adi"
|
aexp à factor
|
aexp.
pcode =factor. pcode
|
factor à (exp)
|
factor.
pcode =exp. pcode
|
factor à num
|
factor.
pcode = "idc"||num.stval
|
factor à id
|
factor.
pcode = "lod"||id.strval
|
Un parse es un procesador de
cualquier lenguaje, por ejemplo, los navegadores llevan internamente
procesadores de varios lenguajes, html, xhtml, xml, css, javascript, etc.
Los compiladores tienen un procesador de comandos
que revisan la sintaxis antes de realizar la compilación.
Así
que en general, los parsers o procesadores, son los motores de procesamiento
del lenguaje (el que aplique en cada momento).