Вы находитесь на странице: 1из 14

UNIVERSIDAD NACIONAL DE LOJA

Facultad de la Energía, las Industrias y los Recursos Naturales no Renovables.


CARRERA DE INGENIERÍA EN SISTEMAS.

Proyecto de Compiladores.

Tema:
Implementación de la Generación de Código.

Autores:
Diego Eduardo Murquincho Puma.
Jessica Cecibel Correa Campoverde.

Docente:
Ing. Willman Chamba.

Fecha:​ 01 de septiembre del 2019.

LOJA – ECUADOR.
ÍNDICE.
1. Objetivos…………………………………………………………………………….. 2
1.1 Objetivo General……………………………………………………………….. 2
1.2 Objetivos Específicos…………………………………………………………. 2
2. Marco Teórico…………………………………………………………………..…… 2
2.1 Análisis Semántico……………………………………………………………… 2
2.1.1. Atributos y acciones semánticas……………………………………….. 2
2.1.2 Ejecución de una acción semántica…………………………………. 2, 3
2.2 Generación de Código…………………………………………………………. 4
2.2.1. Código de tercetos……………………………………….………….. 5, 6, 7
2.3 Tabla de símbolos…………………………………………………………...…. 8
3. Desarrollo..…………………………………………………………………… 8, 9, 10
4. Conclusiones..………………………………………………………………………. 11
5. Recomendaciones…………………………………………………………………. 12
6. Bibliografía………………………………………………………………………….. 13

ÍNDICE DE FIGURAS
FIGURA 1……………………………………………………………………………….. 3
FIGURA 2………………………………………………………………….……………. 4
FIGURA 3………………………………………………………………….……………. 5

ya le en

1
1. Objetivos.
1.1 Objetivo General.
Implementar la Generación de Código para la Gramática y Tokens definidas
de las expresiones aritméticas utilizadas en el analizador sintáctico.

1.2 Objetivos Específicos.


- Traducir el código fuente de varias expresiones aritméticas.
- Hacer uso de las herramientas para la ejecución del programa.

2. Marco Teórico

2.1 Análisis Semántico


Se toma el árbol sintáctico y se realiza una serie de comprobaciones antes de
obtener el analizador semántico, es una fase compleja ya que hay que revisar que
los operadores trabajen sobre tipos compatibles, y si obtienen resultados de tipos
adecuados se los llama subprogramas el cual deben tener parámetros adecuados
tanto en número como tipo. [1]

2.1.1. Atributos y acciones semánticas


Un atributo es una información asociada a un terminal o a un no terminal. Una
acción o regla semántica es un algoritmo que puede acceder a los atributos de los
terminales y/o no terminales. Como acción semántica no sólo se puede poner una
asignación a un atributo, además puede añadirse código. [2]

2.1.2 Ejecución de una acción semántica


Hay dos formas de asociar reglas semánticas con reglas de producción:

● Definición dirigida por sintaxis: consiste en asociar una acción semántica a


una regla de producción, pero dicha asociación no indica cuándo se debe
ejecutar dicha acción semántica.

2
Se supone que en una primera fase se construye el árbol sintáctico completo
y, posteriormente, se ejecutan las acciones semánticas en una secuencia tal
que permita el cálculo de todos los atributos de los nodos del árbol.

● Esquema de traducción: es igual que una definición dirigida por sintaxis


excepto que, además, se asume o se suministra información acerca de
cuándo se deben ejecutar las acciones semánticas. Es más, también es
posible intercalar acciones entre los símbolos del consecuente de una regla
de producción.

La utilización de atributos en un esquema de traducción obedece a reglas del


sentido común una vez que se conocen las transformaciones que un
metacompilador aplica automáticamente a las acciones semántica intermedias, ya
se trate de analizadores LR o con funciones recursivas. [3]

En el caso del análisis ascendente la cosa puede no estar tan clara de primera hora
y resulta conveniente exponer claramente cuáles son estas reglas sobre la
utilización de atributos en acciones semánticas de un esquema LR:

- Un atributo solo puede ser usado (en una acción) detrás del símbolo al que
pertenece.

- El atributo del antecedente solo se puede utilizar en la acción (del final) de su


regla.

- En una acción intermedia sólo se puede hacer uso de los atributos de los
símbolos que la preceden.
[4]

3
2.2 Generación de Código.
En el modelo de análisis y síntesis de un compilador, la etapa inicial traduce un
programa fuente a una representación intermedia a partir de la cual la etapa final
genera el código objeto. [5]

FIGURA 1​. Generador de Código [6]

Según el modelo de arquitectura de un compilador en el que éste se divide en


frontend y backend, la etapa inicial traduce un programa fuente a una
representación intermedia a partir de la cual la etapa final genera el código objeto,
ya sea en forma de código máquina o ensamblador. Los detalles del lenguaje objeto
se confinan en la etapa final, si esto es posible, lo que facilita la reutilización del
frontend para crear otros compiladores del mismo lenguaje pero que generan código
para otras plataformas. De esta forma, aunque a priori puede resultar más fácil
traducir un programa fuente directamente al lenguaje objeto, las dos ventajas
principales de utilizar una forma: [7]

FIGURA 2.​ La construcción de un compilador mediante división en etapa frontend y


etapa backend [8]

4
Los detalles del lenguaje objeto se confinan en la etapa final, si esto es posible.
Aunque un programa fuente se puede traducir directamente al lenguaje objeto,
algunas ventajas de utilizar una forma intermedia independiente de la máquina son:
[9]
1. Se facilita la re-destinación; se puede crear un compilador para una
máquina distinta uniendo una etapa final para la nueva máquina a una etapa
inicial ya existente.
2. Se puede aplicar a la representación intermedia un optimizador de código
independiente de la máquina.

FIGURA 3.​ Ejemplo de la función de un intérprete.

Hay lenguajes que son pseudo interpretados que utilizan un código intermedio
llamado código-P que utiliza lo que se denomina bytecodes (sentencias de un µP
hipotético). Por ejemplo Java utiliza los ficheros .class, éstos tienen unos bytecodes
que se someten a una Java Virtual Machine, para que interprete esas sentencias.
[10]

2.2.1. Código de tercetos


Con el objetivo de facilitar la comprensión de la fase de generación de código, no
nos centraremos en el código máquina puro de ningún microprocesador concreto,
sino en un código intermedio cercano a cualquiera de ellos. Esta aproximación
facilitará, además, la optimización del código generado. Cada una de las
instrucciones que podemos generar posee un máximo de cuatro apartados: [11]

5
● Operando 1º (dirección de memoria donde se encuentra el primer operando).
● Operando 2º (dirección de memoria donde se encuentra el segundo
operando).
● Operador (código de operación)
● Resultado (dirección de memoria donde albergar el resultado, o a la que
saltar en caso de que se trate de una operación de salto).

A este tipo de instrucciones se las denomina códigos de 3 direcciones, tercetos, o


códigos de máximo 3 operandos.

En esencia, los tercetos son muy parecidos a cualquier código ensamblador,


existiendo operaciones para sumar, restar, etc. También existen instrucciones para
controlar el flujo de ejecución, y pueden aparecer etiquetas simbólicas en medio del
código con objeto de identificar el destino de los saltos.

Ejemplo:
Para ilustrar cómo se utiliza el código de tercetos en una gramática vamos a
suponer que nuestra calculadora en lugar de ser una calculadora interpretada es
una calculadora compilada, es decir, en vez de interpretar las expresiones vamos a
generar código intermedio equivalente. [12]

- Pasos de construcción
Antes de comenzar a codificar los analizadores léxico y sintáctico es necesario
plantear exactamente qué se desea hacer y con qué gramática. Para ello se debe
proponer un ejemplo preliminar de lo que debe hacer la calculadora para, a
continuación, crear la gramática que reconozca el lenguaje, asignar los atributos
necesarios y, una vez claro el cometido de las acciones semánticas, comenzar la
codificación. [13]

El objetivo es que la calculadora produzca un texto de salida ante un texto de


entrada. Por ejemplo, si la entrada es:

6
a := 5 * b;
c := b := d := a * (4+v);
c := c := c;
la salida debería ser:

tmp1 = 5 * b
a = tmp1
tmp2 = 4 + v
tmp3 = a * tmp2
d = tmp3
b=d
c=b
c=c
c=c

que es el código de tercetos equivalente.

- Gramática
La gramática de partida basada en reglas de producción es:

prog : asig ';'


| prog asig ';'
| error ';'
| prog error ';'
;
asig : ID ASIG expr
| ID ASIG asig
;
expr : expr '+' expr
| expr '*' expr
| '(' expr ')'

7
| ID
| NUMERO
;

Como puede observarse, no se permite el programa vacío, lo que hace que sea
necesario introducir una nueva regla de error que gestione la posibilidad de que se
produzca algún fallo en la primera asignación. Nótese que si se produce un error en
esta primera asignación, la pila del análisis sintáctico aún no tiene el no terminal
prog a su izquierda, por lo que no es posible reducir por la regla ​prog : prog error
‘;’​, pero sí por la regla ​prog : error ‘;’​.

Por otro lado, la finalización de cada sentencia se hace con un punto y coma ​‘;’​, en
lugar de con retorno de carro, lo que resulta más cercano al tratamiento real de los
lenguajes de programación actuales. [14]

2.3. Tabla de símbolos


Aunque no sean una fase del proceso de compilación, pero es una parte muy
importante del proceso. La tabla de símbolos es una estructura de datos auxiliar
donde se va a guardar información sobre las variables declaradas, las funciones y
procedimientos, sus nombres, sus parámetros y en generar información necesaria
para realizar todo el proceso de compilación.
El compilador debe tener un acceso a estas tablas a lo largo de todo el proceso de
compilación. [15]

3. Desarrollo

Se usa la herramienta de compilador por medio del siguiente algoritmo:

public void GuardarLen() { //Para guardar el archivo.java (texto.java)

try {//Aquí se guardar la estructura de un proyecto java


File archivo = new File("texto.java");
FileWriter escribir = new FileWriter(archivo, true);

8
escribir.write("public class texto{" + "\n");

escribir.write(" public static void main(String[ ] args) {" + "\n");


escribir.write("int base= " + base + ";" + "\n");
escribir.write("int exp= " + exp + ";" + "\n");
escribir.write("int nu1= " + nu1 + ";" + "\n");
escribir.write("int nu2= " + nu2 + ";" + "\n");
escribir.write("int res= " + mostrar() + ";" + "\n");
escribir.append('\n');

escribir.write("System.out.println(\"Resultado es \"+ res);" + "\n");

escribir.write("}");
escribir.append('\n');
escribir.write("}");
escribir.append('\n');

//Cerramos la conexion
escribir.close();

} catch (IOException e) {
System.out.println("Error al escribrir");
}
}

public String leerLenguaje() { // Para leer el archivo.java (texto.java)


File archivo;
StringBuffer sb = null, valor;
String caracter = "";
FileReader fr;
BufferedReader br;
try {
archivo = new File("texto.java");
fr = new FileReader(archivo);
br = new BufferedReader(fr);
sb = new StringBuffer();
if (archivo != null) {
while ((caracter = br.readLine()) != null) {
sb.append(caracter + "\n");
}
}

} catch (Exception e) {
System.out.println("Error AbrirArchivo: " + e);
}
return sb.toString();

9
}
Método del Análisis Semántico.

public static void analisisSemantico(String codigo) {


//StringBuilder codigoFinal = new StringBuilder(codigo);

if(codigo.trim().matches("[+-/*]\\s\\d+\\s\\d+") ||
codigo.trim().matches("[+-/*]\\s[+-/*]\\s\\d+\\s\\d+\\s[+-/*]\\s\\d+\\s\\d+")) {
Expresion expresion = new Expresion(codigo.trim());
System.out.println(expresion.resultado);

resultadoAnalisisSemantico.setText(String.valueOf(expresion.resultado));
}

public class LeerDocumento implements ActionListener {

@Override
public void actionPerformed(ActionEvent arg0) {
String nombreDeArchivo =
AnalizadorSemantico.rutaDeArchivo.getText().trim();
try {

AnalizadorSemantico.resultadoAnalisisLexico.setText(AnalizadorSemantico.analizar(Analiza
dorSemantico.leerArchivo(nombreDeArchivo)));
} catch (IOException e) {
System.out.println(e.getMessage());
}

AnalizadorSemantico.resultadoAnalisisSintactico.setText(AnalizadorSemantico.errores.toStri
ng());
}

10
4. Conclusiones

En conclusión:

➔ El Analizador Semántico, es de gran importancia dentro del proceso de la


compilación de nuestro programa para los códigos en cualquier lenguaje; ya
que, de él depende la revisión del código en busca de errores semánticos y
de esta manera se asegura una coherencia y un sentido válido en el código
según el lenguaje que esté escrito.

➔ Al realizar este trabajo aprendimos a que a pesar de tener un analizador


léxico y sintáctico, es indispensable el tener un generador de código para
traducir y darle sentido al texto que se ingresa en el archivo con extensión .txt

➔ Nuestro generador de código es de gran ayuda para el desarrollo del


software d​e porque permite la reutilización de los front-ends y back-ends y mejora
la calidad de la producción, el establecimiento de estándares de código y el
tiempo de desarrollo de las aplicaciones.

➔ Con el conocimiento adecuado y el buen uso de las herramientas se puede


llegar a obtener un máximo rendimiento del generador de código que se
utilice, así como una buena práctica de lo métodos de construcción de
software utilizando capas o componentes.

11
5. Recomendaciones.
Se recomienda que:

➔ Que en trabajos futuros se mejore el código fuente que hemos propuesto


como una primera versión.

➔ Al momento de hacer el código para un analizador léxico, sintáctico y


semántico, se deben de tener en cuenta todos los posibles casos que la
expresión pueda aceptar, así como saber que variables, expresiones y
operadores a aceptar en nuestro analizador.

12
6. Bibliografía

[1], [15] ​Ruiz Catalán, J. (2010). Teoría e implementación, 448.

[2], [3], [4], [5], [7], [8], [11], [12], [13], [14] ​Gálvez, S., & Mora, M. Á. (2005). ​Java
a tope: Traductores y Compiladores con Lex/Yacc, JFlex/Cup y JavaCC.​
Universidad de Málaga​. Retrieved from
http://www.lcc.uma.es/~galvez/ftp/libros/Compiladores.pdf

[6] ​Compiladores : Generación de Código Generación de Código. (n.d.).

[9], [10] ​Sierra, M. A. (n.d.). Tema 7 Generaci ó n de C ó digo C ó digo de Tercetos.

13

Вам также может понравиться