miércoles, 20 de noviembre de 2019

Generador de código objeto

 
Generación de código objeto

 El generador de código objeto como lo menciona (Urbina, 2011) transforma el código Intermedio optimizado en código objeto de bajo nivel. Toma código intermedio y genera Código objeto para la máquina considerada Es la parte más próxima a la arquitectura de la Máquina. Habitualmente, se escriben ``a mano´´ desarrollo a medida´ para cada máquina Específica.




REGISTROS

¿Qué son?

Los registros son la memoria principal de la computadora. Existen diversos registros de propósito general y otros de uso exclusivo. Algunos registros de propósito general son utilizados para cierto tipo de funciones. Existen registros acumuladores, puntero de instrucción, de pila, etc.

Los registros son espacios físicos dentro del microprocesador con capacidad de 4 bits hasta 64 bits dependiendo del microprocesador que se emplee.

¿Quiénes lo utilizan?


Antes de nada, para el desarrollo de esta parte hablaremos indistintamente de registros de activación o de marcos de pila. Esto se debe a que en la documentación encontrada sobre el manejo de los registros ebp y esp se hace mención a dicho concepto de marco de   pila.   Puesto   que   el   lenguaje   permite   recursividad,   los   registros   de   activación   se asignan dinámica mente. 




Distribución

La UCP o CPU tiene 14 registros internos, cada uno de ellos de 16 bits (una palabra). Los bits están enumerados de derecha a izquierda, de tal modo que el bit menos significativo es el bit 0.
Los registros se pueden clasificar de la siguiente forma: 

Registros de datos:

AX: Registro acumulador. Es el principal empleado en las operaciones aritméticas.
BX: Registro base. Se usa para indicar un desplazamiento.
CX: Registro contador. Se usa como contador en los bucles.
DX: Registro de datos.

Estos registros son de uso general y también pueden ser utilizados como registros de 8 bits, para utilizarlos como tales es necesario referirse a ellos como por ejemplo: AH y AL, que son los bytes alto (high) y bajo (low) del registro AX. Esta nomenclatura es aplicable también a los registros BX, CX y DX. 

Registros de segmentos: 

CS: Registro de segmento de código. Contiene la dirección de las instrucciones del programa. 
DS: Registro segmento de datos. Contiene la dirección del área de memoria donde se encuentran los datos del programa.
SS: Registro segmento de pila. Contiene la dirección del segmento de pila. La pila es un espacio de memoria temporal que se usa para almacenar valores de 16 bits (palabras).
ES: Registro segmento extra. Contiene la dirección del segmento extra. Se trata de un segmento de datos adicional que se utiliza para superar la limitación de los 64Kb del segmento de datos y para hacer transferencias de datos entre segmentos.

Registros punteros de pila:

SP: Puntero de la pila. Contiene la dirección relativa al segmento de la pila.
BP: Puntero base. Se utiliza para fijar el puntero de pila y así poder acceder a los elementos de la pila.

Registros índices:

 SI: Índice fuente.
 DI: Índice destino.


¿Cuales su aplicación en la generación de códigos?

1. usar el registro de y si está en un registro que no tiene otra variable, y además y no
está viva ni tiene uso posterior. Si no:
2. usar un registro vacío si hay. Si no:
3. usar un registro ocupado si op requiere que x esté en un registro o si x tiene uso
Posterior. Actualizar el descriptor de registro. Si no:
4. usar la posición de memoria de x

Ensambladores





Son los encargados de traducir los programas escritos en lenguaje ensamblador a lenguaje máquina.

Compiladores

Son programas que leen el código fuente y lo traducen o convierten a otro lenguaje. Estos programas te muestran los errores existentes en el código fuente.





Etapas del proceso de compilación:


Edición. Esta fase consiste en escribir el programa empleando algún lenguaje y un editor. Como resultado nos dará el código fuente de nuestro programa.
Compilación. En esta fase se traduce el código fuente obtenido en la fase anterior a código máquina. Si no se produce ningún error se obtiene el código objeto.
En caso de errores el compilador los mostraría para ayudarnos a corregirlos y se procedería a su compilación de nuevo, una vez corregidos.
Linkado. Esta fase consiste en unir el archivo generado en la fase dos con determinadas rutinas internas del lenguaje, obteniendo el programa ejecutable.
Existen dos tipos de linkados:


linkado estático: Los binarios de las librerías se añaden a nuestros binarios compilados generando el archivo ejecutable.
Linkado dinámico: no se añaden las librerías a nuestro binario sino que hará que se carguen en memoria las librerías que en ese momento se necesiten.
Una vez traducido, compilado y linkado el archivo esta listo para su ejecución donde también podrán surgir problemas y fallos, para los cuales tendríamos que volver a realizar todo el proceso anteriormente citado, de modo que puedan ser corregidos.

Por este motivo es importante realizar numerosas pruebas en tiempo de ejecución antes de presentar el programa al cliente.

Otro sistema para la ejecución de nuestro código fuente es mediante el uso de intérpretes (estos no se encontrarían dentro de los traductores).

Intérpretes

Los intérpretes realizan la traducción y ejecución de forma simultanea, es decir, un intérprete lee el código fuente y lo va ejecutando al mismo tiempo.

Las diferencias entre un compilador y un intérprete básicamente son:


Un programa compilado puede funcionar por si solo mientras que un código traducido por un intérprete no puede funcionar sin éste.
Un programa traducido por un intérprete puede ser ejecutado en cualquier máquina ya que, cada vez que se ejecuta el intérprete, tiene que compilarlo.
Un archivo compilado es mucho más rápido que uno interpretado.




lunes, 21 de octubre de 2019

Glosario tecnico- Nemotecnia Lenguaje ensamblador


Conceptos básicos.
Lenguaje de alto nivel: es aquel que se aproxima más al lenguaje natural humano que al lenguaje binario de las computadoras. Su función principal radica en que a partir de su desarrollo, existe la posibilidad de que se pueda utilizar el mismo programa en distintas máquinas, es decir que es independiente de un hardware determinado. La única condición es que la PC tenga un programa conocido como traductor o compilador, que lo traduce al lenguaje específico de cada máquina. Entre estos lenguajes figuran ejemplos como PASCAL, BASIC, FORTRAN y C++. (Lanzillotta, 2004).
•Lenguaje de bajo nivel: también llamado lenguaje ensamblador, permite al programador escribir instrucciones de un programa usando abreviaturas del inglés, también llamadas palabras nemotécnicas, tales como: ADD, DIV, SUB, etc. Un programa escrito en un lenguaje ensamblador tiene el inconveniente de que no es comprensible para la computadora, ya que, no está compuesto por ceros y unos. Para traducir las instrucciones de un programa escrito en un lenguaje ensamblador a instrucciones de un lenguaje máquina hay que utilizar un programa llamado ensamblador. (Pes, 2009).
  Lenguaje ensamblador: es un tipo de lenguaje de bajo nivel utilizado para escribir programas informáticos, y constituye la representación más directa del código máquina específico para cada arquitectura de computadoras legible por un programador. (Wikipedia, 2009).
• Lenguaje máquina: El lenguaje máquina es el único que entiende la computadora digital, es su "lenguaje natural". En él sólo se pueden utilizar dos símbolos: el cero (0) y el uno (1). Por ello, al lenguaje máquina también se le denomina lenguaje binario. (Pes, 2009).
  Compilador: los compiladores son programas o herramientas encargadas de compilar. Un compilador toma un texto (código fuente) escrito en un lenguaje de alto nivel y lo traduce a un lenguaje comprensible por las computadoras (código objeto).


Clasificación de los ensambladores.
 Ensambladores Cruzados (Cross-Assembler): Se denominan así los ensambladores que se utilizan en una computadora que posee un procesador diferente al que tendrán las computadoras donde va a ejecutarse el programa objeto producido. El empleo de este tipo de traductores permite aprovechar el soporte de medios físicos (discos, impresoras, pantallas, etc.), y de programación que ofrecen las máquinas potentes para desarrollar programas que luego los van a ejecutar sistemas muy especializados en determinados tipos de tareas.
                                                                                                                                                     Ensambladores Residentes: Son aquellos que permanecen en la memoria principal de la computadora y cargan, para su ejecución, al programa objeto producido. Este tipo de ensamblador tiene la ventaja de que se puede comprobar inmediatamente el programa sin necesidad de transportarlo de un lugar a otro, como se hacía en cross-assembler, y sin necesidad de programas simuladores.
 Microensambladores: Generalmente, los procesadores utilizados en las computadoras tienen un repertorio fijo de instrucciones, es decir, que el intérprete de las mismas interpretaba de igual forma un determinado código de operación.
 Macroensambladores: Son ensambladores que permiten el uso de macroinstrucciones (macros). Debido a su potencia, normalmente son programas robustos que no permanecen en memoria una vez generados el programa objeto. Puede variar la complejidad de los mismos, dependiendo de las posibilidades de definición y manipulación de las macroinstrucciones, pero normalmente son programas bastantes complejos, por lo que suelen ser ensambladores residentes.
 Ensambladores de una fase: Estos ensambladores leen una línea del programa fuente y la traducen directamente para producir una instrucción en lenguaje máquina o la ejecuta si se trata de una pseudoinstrucción. También va construyendo la tabla de símbolos a medida que van apareciendo las definiciones de variables, etiquetas, etc.
Ensambladores de dos fases: Los ensambladores de dos fases se denominan así debido a que realizan la traducción en dos etapas. En la primera fase, leen el programa fuente y construyen una tabla de símbolos; de esta manera, en la segunda fase, vuelven a leer el programa fuente y pueden ir traduciendo totalmente, puesto que conocen la totalidad de los símbolos utilizados y las posiciones que se les ha asignado. Estos ensambladores son los más utilizados en la actualidad.
Instrucciones simbólicas
Una instrucción en ensamblador consta de códigos de operación, operaciones, operandos y comentarios.
La etiqueta, es el símbolo que añadido a una instrucción permite que se pueda referenciar simbólicamente en el programa.
El código de operación, es generalmente un símbolo, aunque en algunos sistemas es un entero octal, hexadecimal o decimal que indica la operación que se quiere realizar.
El campo operando, es un campo de dirección de datos que puede estar dividido en varios subcampos de acuerdo con la constitución interna de la computadora correspondiente.
El comentario, que no es obligatorio, no es procesado por el ensamblador. Nos dice que las instrucciones pueden ser de formato fijo, formato libre y formato mixto.

CONJUNTO DE INSTRUCCIONES (Microprocesadores 8086/8088)
Se pueden clasificar en los siguientes grupos:
Instrucciones de Transferencia de Datos
Estas instrucciones mueven datos de una parte a otra del sistema; desde y hacia la memoria principal, de y a los registros de datos, puertos de E/S y registros de segmentación.
Las instrucciones de transferencia de datos son las siguientes:
MOV                  transfiere
XCHG                intercambia
IN                      entrada
OUT                  salida
XLAT                 traduce usando una tabla
LEA                   carga la dirección efectiva
LDS                   carga el segmento de datos
LES                   carga el segmento extra
LAHF                carga los indicadores en AH
SAHF                guarda AH en los indicadores
PUSH FUENTE (sp) ←←fuente
POP DESTINO  destino←←(sp)
Control de Bucles (instrucciones simples)

Éstas posibilitan el grupo de control más elemental de nuestros programas. Un bucle es un bloque de código que se ejecuta varias veces. Hay 4 tipos de bucles básicos:
• Bucles sin fin
• Bucles por conteo
• Bucles hasta
• Bucles mientras

Las instrucciones de control de bucles son las siguientes:
• INC    incrementar
• DEC  decrementar
• LOOP realizar un bucle
• LOOPZ, LOOPE realizar un bucle si es cero
• LOOPNZ, LOOPNE realizar un bucle si no es cero
• JCXZ salta si CX es cero

Instrucciones de Prueba, Comparación y Saltos.
Este grupo es una continuación del anterior, incluye las siguientes instrucciones:
TEST   verifica
CMP   compara
JMP   salta
JE, JZ   salta si es igual a cero
JNE, JNZ  salta si no igual a cero
JS   salta si signo negativo
JNS   salta si signo no negativo
JP, JPE  salta si paridad par
JNP, JOP  salta si paridad impar
JO   salta si hay capacidad excedida
JNO   salta si no hay capacidad excedida
JB, JNAE  salta si por abajo (no encima o igual)
JNB, JAE  salta si no está por abajo (encima o igual)
JBE, JNA  salta si por abajo o igual (no encima)
JNBE, JA  salta si no por abajo o igual (encima)
JL, JNGE  salta si menor que (no mayor o igual)
JNL, JGE  salta si no menor que (mayor o igual)
JLE, JNG  salta si menor que o igual (no mayor)
JNLE, JG  salta si no menor que o igual (mayor)
Instrucciones de Llamado y Retorno de Subrutinas.

Para que los programas resulten eficientes y legibles tanto en lenguaje ensamblador como en lenguaje de alto nivel, resultan indispensables las subrutinas:

CALL   llamada a subrutina
RET   retorno al programa o subrutina que llamó
Instrucciones Aritméticas.
Estas instrucciones son las que realiza directamente el 8086/8088

a. Grupo de adición:
ADD   suma
ADC   suma con acarreo
AAA   ajuste ASCII para la suma
DAA   ajuste decimal para la suma

b. Grupo de sustracción:
SUB  resta
SBB  resta con acarreo negativo
AAS  ajuste ASCII para la resta
DAS  ajuste decimal para la resta

c. Grupo de multiplicación:
MUL   multiplicación
IMUL   multiplicación entera
AAM   ajuste ASCII para la multiplicación

d. Grupo de división:
DIV   división
IDIV   división entera
AAD   ajuste ASCII para la división

e. Conversiones:
CBW   pasar octeto a palabra
CWD   pasar palabra a doble palabra
NEG   negación

f. Tratamiento de cadenas:
Permiten el movimiento, comparación o búsqueda rápida en bloques de datos:
MOVC   transferir carácter de una cadena
MOVW   transferir palabra de una cadena
CMPC   comparar carácter de una cadena
CMPW   comparar palabra de una cadena
SCAC   buscar carácter de una cadena
SCAW   buscar palabra de una cadena
LODC   cargar carácter de una cadena
LODW   cargar palabra de una cadena
STOC   guardar carácter de una cadena
STOW   guardar palabra de una cadena
REP   repetir
CLD   poner a 0 el indicador de dirección
STD   poner a 1 el indicador de dirección

Instrucciones Lógicas.

Son operaciones bit a bit que trabajan sobre octetos o palabras completas:
NOT   negación
AND   producto lógico
OR   suma lógica
XOR   suma lógica exclusiva

Instrucciones de Desplazamiento, Rotación y Adeudos.
Básicamente permiten multiplicar y dividir por potencias de 2
SHL, SAL  desplazar a la izquierda (desplazamiento aritmético)
SHR   desplazar a la derecha
SAR   desplazamiento aritmético a la derecha
ROL   rotación a la izquierda
ROR   rotación a la derecha
RCL   rotación con acarreo a la izquierda
RCR   rotación con acarreo a la derecha
CLC   borrar acarreo
STC   poner acarreo a 1

Instrucciones de Pila.
Una de las funciones de la pila del sistema es la de salvaguardar (conservar) datos (la otra es la de salvaguardar las direcciones de retorno de las llamadas a subrutinas):
PUSH   introducir
POP   extraer
PUSHF   introducir indicadores
POPF   extraer indicadores

Instrucciones de Control del microprocesador.
Hay varias instrucciones para el control de la CPU, ya sea a ella sola, o en
conjunción con otros procesadores:
NOP   no operación
HLT   parada
WAIT   espera
LOCK   bloquea
ESC   escape

Instrucciones de Interrupción.
STI   poner a 1 el indicador de interrupción
CLI   borrar el indicador de interrupción
INT   interrupción
INTO   interrupción por capacidad excedida (desbordamiento)
IRET   retorno de interrupción


lunes, 30 de septiembre de 2019

2.3.2 Expresiones

2.3.1 Variables y constantes

Variables y constantes

• Una constante es un dato numérico o alfanumérico que no cambia durante la ejecución del programa.

Ejemplo: pi = 3.1416

• Una variable es un espacio en la memoria de la computadora que permite almacenar temporalmente un dato durante la ejecución de un proceso, su contenido puede cambiar durante la ejecución del programa.

Ejemplo: area=pi*radio^2

Las variables son: el radio, el area y la constate es pi

• Las declaraciones de variables y constantes deben
separarse de tal manera que queden las expresiones
una por una de manera simple.

2.3 Esquema de generación

Esquema de generación

• Los esquemas de generación son las estrategias o acciones que se deberán realizarse y tomarse en cuenta en el momento de generar código intermedio.

• Los esquemas de generación dependen de cada lenguaje.

2.2.4 Cuádruplos

Cuádruplos

Es una estructura tipo registro con cuatros campos que se llaman:
Operador
Operando1
Operando2
Resultado
Donde operando1, operando2 y resultado pueden ser constantes, identificadores y variables temporales definidos por el compilador mientras que operador representa una operación arbitraria.

Operador
Operando1
Operando2
Resultado
*
C
D
T1
+
B
T1
T2
=
T2
A
EJEMPLO:
A := B + C * D

2.2.3 Triplos

Triplos. 

En la historia de los compiladores han sido utilizadas una amplia variedad de representaciones intermedias como lo es la siguiente clase de representación de código intermedio de un árbol de 3 direcciones,2 para los operandos y una para la ubicación del resultado. esta clase incluye un amplio número de representaciones diferentes entre las cuales encontramos cuadruplos y triples. la principal diferencia entre estas notaciones y la notación postfija es que ellos incluyen referencias explicitas para los resultados de los cálculos intermedios, mientras que la notación posfija los resultados son implícitos al representarlos en una pila.
§     La diferencia entre triples y cuadruplos es que con los triples es referenciado el valor intermedio hacia el número del triple que lo creo, pero en los cuádruplos requiere que ellos tengan nombres implícitos.
§     Los triples tienen una ventaja obvia de ser más consistente, pero ellos dependen de su posición, y hacen que la optimización presente cambios de código mucho más compleja.
Para evitar tener que introducir nombres temporales en la tabla de símbolos, se hace referencia a un valor temporal según la posición de la proposición que lo calcula. Las propias instrucciones representan el valor del nombre temporal. La implementación se hace mediante registros de solo tres campos (op, arg1, arg2).
§     En la notación de tripletes se necesita menor espacio y el compilador no necesita generar los nombres temporales. Sin embargo, en esta notación, trasladar una proposición que defina un valor temporal exige que se modifiquen todas las referencias a esa proposición. Lo cual supone un inconveniente a la hora de optimizar el código, pues a menudo es necesario cambiar proposiciones de lugar.
§     Una forma de solucionar esto consiste en listar las posiciones a las tripletas en lugar de listar las tripletas mismas. De esta manera, un optimizador podría mover una instrucción reordenando la lista, sin tener que mover las tripletas en si.

2.2.2 Código P.

Generación de Código Intermedió

*Después de los análisis 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 proposiciones while 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).