actividades unidad2 automatas ii
DESCRIPTION
unidad 2 de la materia automatas 2 de la carrera ing. en sistemas computacionalesTRANSCRIPT
TECNOLÓGICO NACIONAL DE MÉXICOINSTITUTO TECNOLOGICO DE ACAPULCO
“Educación Tecnológica con compromiso social”
Ingeniería en Sistemas ComputacionalesCarrera
Lenguajes y autómatas IIAsignatura
Actividades de aprendizajeUnidad 2Generación de código intermedio
Competencia específica a desarrollarAplicar las herramientas para desarrollar una máquina virtual que ejecute código
intermedio a partir del código fuente de un lenguaje prototipo.
Profesor: Silvestre Bedolla Solano.
Integrantes del equipo 7
Alumnos:
Jimenez Gonzalez Alam Emmanuel 10320978Hidalgo Sánchez Teresita Berenice 11320155Arancibia García Ángel Andrés 10320918
Acapulco, Guerrero, 6 de Diciembre del 2015
1
Contenido
Introducción....................................................................................................................................3
Actividad 1: Reporte de la aplicación de los tipos de notación para la conversión de expresiones: Infija, prefija y posfija, como resultado de su ejecución en un programa diseñado en C#.............................................................................................................................6
Actividad 2: Reporte de la aplicación del manejo de tipos en las expresiones y el uso de operadores...................................................................................................................................13
Actividad 3: Código intermedio de la estructura de un lenguaje de programación de alto nivel...............................................................................................................................................14
Actividad 4: Lenguaje prototipo aplicando las acciones construidas a la gramática.........27
Actividad 5: Tres programas tipo, usando la gramática definida de su lenguaje de programación...............................................................................................................................37
Actividad 6: Reporte del avance por equipo del proyecto final consistente en desarrollar software de base: traductor, intérprete o compilador.............................................................46
Conclusión....................................................................................................................................63
Bibliografías.................................................................................................................................64
2
Introducción
Generación de código intermedio
• El objetivo del código intermedio es reducir el número de programas necesarios para construir traductores, y permitir más fácilmente la transportabilidad de unas máquinas a otras.
• Después de los análisis sintáctico y semántico, algunos compiladores generan una representación intermedia explicita del programa fuente. Esta representación intermedia debe tener dos propiedades importantes; debe ser fácil de producir y fácil de traducir al programa objeto.
Lenguajes intermedios
Un lenguaje intermedio es el lenguaje de una máquina abstracta diseñada para ayudar en el análisis de los programas de computadora. el término viene de su uso en los compiladores, donde un compilador primero traduce el código fuente de un programa, en una forma más apropiada para las transformaciones de mejora del código (forma usualmente llamada bytecode), como un paso intermedio antes de generar el archivo objeto o el código máquina para una máquina específica.
Características destacadas:
Su principal ventaja es la portabilidad, pues el mismo código puede ser ejecutado en diferentes plataformas y arquitecturas.
Esta ventaja la tiene también los lenguajes interpretados, aunque generalmente con mejor rendimiento. Por esto, muchos lenguajes interpretados se compilan a bytecode y después son ejecutados por un intérprete de bytecode.
en java generalmente se transmite el bytecode a la máquina receptora y esta se encarga de utilizar un compilador just-in-time para traducirlo a código máquina antes de su ejecución.
Tipos de lenguaje intermedio
Hay tres tipos de lenguaje intermedio los cuales son:
Tipo 1
Es una representación más abstracta y uniforme que un lenguaje máquina concreto. Su misión es descomponer las expresiones complejas en binarias y las sentencias complejas en sentencias simples.
3
Ventajas:
• permite una fase de análisis (análisis semántico) independiente de la máquina.
*bytecode: código intermedio entre el código fuente y el código máquina. suele tratárselo como un fichero binario que contiene un programa ejecutable similar a un módulo objeto.
• Se pueden realizar optimizaciones sobre el código intermedio (las complejas rutinas de optimización son independientes de la máquina.
Desventajas:
• perdida de eficiencia (no permite una compilación de una sola pasada).
• introduce en el compilador una nueva fase de traducción.
Tipo 2
Tipo de lenguajes intermedios:
• Árbol sintáctico.
• Árbol sintáctico abstracto (todos los nodos del árbol representan símbolos terminales, los nodos hijos son operandos y los nodos internos son operadores).
• Grafo dirigido a cíclico (gda).
• Notación posfija.
Tipo 3
• Tripelatas
Ejemplo:
d = a + b * c [1] (*, b, c) [2] (+, a, [1]) [3] (=, d, [2]) <operador>,<operando_1>, <operando_2>
4
• Cuartetos
Ejemplo:
d = a + b * c (*, b, c, temp1) (+, a, temp1, temp2) (=, temp2, ², d)
• Los lenguajes intermedios nos sirven para representar la producción final de nuestro lenguaje fuente.
• existen muchos lenguajes intermedios, la mayoría de ellos son una representación más simplificada del código original para facilitar la traducción hacia el código final.
• Por ejemplo al compilar un programa en c en windows o dos, se produce un código objeto con extensión .obj para que posteriormente el enlazador cree finalmente el código executable .exe
• En sistemas basados en unix, también ocurre algo similar generándose un archivo .o y el executablea.out
• Otros lenguajes intermedios famosos son los generados para la máquina virtual de java el bytecode; y para la máquina virtual de .net el misl para luego ejecutarse en tiempo de ejecución jit (just in time).
Actividad 1: Reporte de la aplicación de los tipos de notación para la conversión de expresiones: Infija, prefija y posfija, como resultado de su ejecución en un programa diseñado en C#.
5
Expresiones infijas, prefija, posfija
• PreFija:
La Expresión o Notación PreFija nos indica que el operador va antes de los operandos sus características principales son:
-Los operandos conservan el mismo orden que la notación infija equivalente.
-No requiere de paréntesis para indicar el orden de precedencia de operadores ya que el es una operación.
-Se evalúa de izquierda a derecha hasta que encontrémosle primer operador seguido inmediatamente de un par de operandos.
-Se evalúa la expresión binaria y el resultado se cambia como un nuevo operando. Se repite este hasta que nos quede un solo resultado.
Notación prefija: El orden es operador, primer operando, segundo operando
• InFija:
La Expresión o Notación InFija es la forma más común que utilizamos para escribir expresiones matemáticas, estas notaciones se refiere a que el operador esta entre los operandos. La notación infija puede estar completamente patentizada o puede basarse en un esquema de precedencia de operadores así como el uso de paréntesis para invalidar los arreglos al expresar el orden de evaluación de una expresión:
3*4=12
3*4+2=14
3*(4+2)=18
Notación infija: La notación habitual. El orden es primer operando, operador, segundo operando.
• PosFija:
Como su nombre lo indica se refiere a que el operador ocupa la posición después de los operandos sus características principales son:
-El orden de los operandos se conserva igual que la expresión infija equivalente no utiliza paréntesis ya que no es una operación ambigua.
6
-La operación posfija no es exactamente lo inverso a la operación prefija equivalente:
(A+B)*C AB+C*
Notación postfija: El orden es primer operando, segundo operando, operador.
Ejemplo:
Si deseamos representar las expresiones (2+(3*4)) = x y ((2+3)*4)= x en las tres notaciones mencionadas, el resultado sería:
(2+(3*4)) = x ((2+3)*4) = x
Notación prefija = + 2 * 3 4 x = * + 2 3 4 x
Notación infija 2+3*4 = x (2+3)*4 = x
Notación posfija 2 3 4 * + x = 2 3 + 4 * x =
Desarrollo
Código
Conversión de expresiones infija a posfija
#include <stdio.h>
bool isOperator(char c){if (c=='+' || c=='-' || c=='*' || c=='/' || c=='^' || c=='(' || c==')')return true;return false;}int precedencia (char x, char y){int prec1, prec2;
switch(x){case '+':prec1=1;break;
case '-':
7
prec1=2;break;
case '*':prec1=3;break;
case '/':prec1=4;break;
case '^':prec1=5;}
switch(y){case '+':prec2=1;break;
case '-':prec2=2;break;
case '*':prec2=3;break;
case '/':prec2=4;break;
case '^':prec2=5;}
return prec1-prec2;}
int main(){char *inf = new char[401];char *post = new char[401];
8
char *pila = new char[200];int ptr=0;
int n, j=0;
scanf("%d", &n);
while(n>0){scanf("%s", inf);
for(int i=0; inf[i]!='\0'; i++){if (!isOperator(inf[i])){post[j]=inf[i];j++;}else if (inf[i]=='('){pila[++ptr]='(';}else if (inf[i]==')'){while(pila[ptr]!='(' && ptr>0){post[j]=pila[ptr--];j++;}if (pila[ptr]=='(')ptr--;}else{while(precedencia(inf[i], pila[ptr]) < 0 && pila[ptr]!='(' && ptr>0){post[j]=pila[ptr--];j++;}pila[++ptr]=inf[i];}
}while(ptr>0)
9
{post[j]=pila[ptr--];j++;}post[j]='\0';printf("%s\n", post);j=0;ptr=0;n--;}return 0;}}
Pantallazos
Ejemplo: conversión de expresiones infijas a posfija
Entrada de expresiones infijas:
3 números de elementos que se desean consultar
(a+(b*c))
((a+b)*(z+x))
((a+t)*((b+(a+c))^(c+d)))
Salida de expresiones posfijas:
abc*+
ab+zx+*
at+bac++cd+^*
10
Conclusiones
Para concluir tenemos que las pilas las listas enlazadas, todos sus métodos y las diferentes expresiones son una de las herramientas utilizadas para programar y procesar de una manera automatizada todos los sistemas que usamos día tras día en relación a técnicas o métodos para agilizar operaciones. Estas herramientas son uno de los instrumentos más usados en la programación para estructurar y organizar información.
Bibliografía
http://www.monografias.com/trabajos44/pilas-listas-expresiones/pilas-listas-expresiones2.shtml#ixzz3nXiPGmfk
http://www.lawebdelprogramador.com/foros/Dev-C/724318-Transformar-notacion-i
11
Actividad 2: Reporte de la aplicación del manejo de tipos en las expresiones y el uso de operadores.
Introducción
Expresiones
Una expresión es una combinación de operadores y operando. Los datos u operando pueden ser constantes, variables y llamadas a funciones. Además, dentro de una expresión pueden encontrarse subexpresiones encerradas entre paréntesis. Por ejemplo, la siguiente expresión matemática:
( 3) cos( ) 2
x + b − ⋅ αCuando se ejecuta una sentencia de código que contiene una expresión, ésta se evalúa. Al evaluarse la expresión toma un valor que depende del valor asignado previamente a las variables, las constantes y los operadores y funciones utilizadas y la secuencia de la ejecución de las operaciones correspondientes. Este valor resultante de la evaluación de la expresión será de un determinado tipo de dato. Por ejemplo, de un tipo numérico entero (integer, shortint…), de un tipo real o de un tipo lógico o booleano.
Operadores
En el código fuente de un programa un operador es un carácter o una secuencia de caracteres. Por ejemplo: +, *, div o shr. Los operadores definen las operaciones que van a realizarse con los datos u operando. En TurboPascal existen distintos tipos de operadores. Por un lado, pueden clasificarse, dependiendo del número de operando, en unarios o unitarios (un operando) y binarios (dos operandos). Por otro lado, pueden clasificarse, dependiendo del tipo de operando y de su resultado, en operadores aritméticos, de cadenas de caracteres, de relación, lógicos o booleanos, de bit y de conjuntos. Algunos operadores están sobrecargados, lo que significa que la operación que representan depende del número o tipos de operando sobre los que actúa. De esta forma, por ejemplo el operador + puede hacer referencia a la suma de valores numéricos, a la concatenación de caracteres o a la unión de conjuntos dependiendo del tipo de sus operando.
Desarrollo
12
Operador de asignación
El operador de asignación se representa por la secuencia de caracteres:=. Permite asignar a una variable el valor de una expresión. Por ejemplo:
var x,y,z: real;Beginx:=12.5;y:=-5.7;z:=2*x+3*y;
Operadores aritméticos
Los operadores aritméticos operan sobre valores de tipo entero o real. Los operadores aritméticos se resumen en la Tabla 12. En el caso del operador unitario de cambio de signo, el resultado es del mismo tipo que el del operando; en el caso de los tres primeros operadores binarios (suma, resta y producto) si ambos operandos son enteros el resultado es entero, si alguno es real el resultado es real. Con el fin de mantener la coherencia durante la operación, para un operador binario, operandos con distinto tipo se convierten a un mismo tipo común antes de la operación. El tipo común es el tipo de dato predefinido de TurboPascal con el menor intervalo de representación que incluye los valores de ambos operandos. Un concepto equivalente sería el de mínimo común múltiplo.
Por ejemplo, el tipo común de dos tipos byte e integer es el tipo integer. El tipo común de dos tipos integer y word es un longint. El tipo común de dos tipos integer y real es un real. La operación se lleva a cabo dentro del intervalo de representación y con la precisión de este tipo común y el resultado es también de este tipo común.
Operadores de relación
Los operadores de relación son operadores binarios en los que los operandos son ordinales, reales o de cadena. Los dos primeros operadores sirven también para
13
operandos de tipo record y punteros. Todos ellos dan lugar a resultados de tipo booleano. Losoperadores de relación se resumen en la Tabla 13.
No hay que confundir el operador lógico igualdad =, con el operador de asignación:=, que asigna valores a variables o funciones. La expresión a=b compara los valores almacenados en la variables a y b y devuelve true o false según el resultado, mientras que la sentencia a:=b; asigna a la variable a el valor almacenado en la variable b.
Operadores lógicos o booleanos
Los operadores lógicos o booleanos realizan operaciones con operandos de tipo lógico o booleano y tiene como resultado un dato también del mismo tipo. Los operadores booleanos definidos en TurboPascal se resumen en la Tabla:
14
Niveles de prioridad de los operadores
Una expresión puede contener distintos tipo de operadores mientras cada operador trabaje con operandos del tipo adecuado. La pregunta que surge a continuación es: ¿qué operación de las que se pueda encontrar en una expresión se realiza antes que las demás? Los niveles de prioridad entre operadores en una misma expresión se resumen en la Tabla 16.
Las secuencias de operadores de igual prioridad normalmente se evalúan de izquierdaa derecha dentro de una expresión, aunque, en algunos casos, el compilador puede reordenar los operandos durante el proceso de compilación para generar código objeto óptimo para su posterior ejecución. En muchas ocasiones se recomienda el uso de los paréntesis para hacer que las expresiones sean más claras y fáciles de entender. En la Tabla 17 se muestran algunos ejemplos de expresiones y de los correspondientes resultados al ser evaluadas.
Las reglas de evaluación de expresiones pueden resumirse en las siguientes:
a) Un operando situado entre dos operadores de diferente prioridad se liga al operador de mayor prioridad.
b) Un operando situado entre dos operadores de igual prioridad se liga al operador de la izquierda.
c) Las expresiones entre paréntesis se evalúan primeramente para ser tratadas como operandos simples.
15
Conclusión
En este tema se presentan los siguientes elementos de la programación: las expresiones y los operadores. Se define el concepto de expresión y se continúa con el estudio de los distintos tipos de operadores: aritmético, de relación, boléanos y de bit. En el apartado final se analizan las reglas de prioridad de los operadores que se siguen en la evaluación de expresiones de todo tipo. Una expresión es una combinación de operadores y operandos. Los datos u operandos pueden ser constantes, variables y llamadas a funciones. Además, dentro de una expresión pueden encontrarse subexpresiones encerradas entre paréntesis.
Bibliografías
García-Beltrán, A., Martínez, R. y Jaén, J.A. Métodos Informáticos en TurboPascal, Ed.Bellisco, 2ª edición, Madrid, 2002
Joyanes, L. Fundamentos de programación, Algoritmos y Estructuras de Datos, McGrawHill, Segunda edición, 1996
Duntemann, J. La Biblia de TurboPascal, Anaya Multimedia, Madrid, 1991
16
Actividad 3: Código intermedio de la estructura de un lenguaje de programación de alto nivel.
Código intermedio
Existen códigos intermedios de diversos tipos que varían en cuanto a su sencillez, lo próximos que están a las maquinas reales y lo fácil que es trabajar con ellos. Nosotros nos centraremos en un tipo de código que se parece bastante al lenguaje ensamblador. Existen otros tipos de código intermedio que representan los programas como árboles o grafos. También existen representaciones mixtas que combinan grafos o árboles y representaciones lineales.
El formato que usaremos para las operaciones binarias es similar al del ensamblador MIPS:
Op dst, op1, op2, donde:
* op es un operador.
*dst es el registro destino de la operación.
*op1 y op2 son los operandos. Si op termina en i, el segundo operando es un valor inmediato
(Entero o real).
En caso de que el operador sea unario, la forma de la instrucción es op dst, op1. Para acceder
A memoria utilizaremos instrucciones de acceso a memoria usando un registro base y un desplazamiento. Para leer de memoria utilizamos lw dst, desp (base) donde dst es el registro destino, desp es un entero que representa el desplazamiento y base es el registro base. Si queremos acceder a una dirección absoluta, utilizamos $zero como registro base, aprovechando que este registro siempre contiene el valor 0. Para escribir en memoria utilizamos sw fnt, desp (base) donde fnt es el registro fuente y el resto tiene el mismo significado que antes.
17
Por ejemplo, la sentencia a:= b*(-c), donde a es una variable local en la dirección 1 respecto al registro $fp y b y c son variables globales en las direcciones 1000 y 1001, se puede traducir como:
Lw $r1, 1000($zero)
lw $r2, 1001($zero)
multi $r3, $r2, -1
mult $r4, $r1, $r3
sw $r4, 1($fp)
Uno de los objetivos que suele perseguirse es reducir al mínimo el número de registros utilizados.
En nuestro caso, podemos emplear dos:
lw $r1, 1000($zero)
lw $r2, 1001($zero)
multi $r2, $r2, -1
mult $r1, $r1, $r2
sw $r1, 1($fp)
Si las variables estuvieran en los registros $r1, $r2 y $r3, podríamos incluso no utilizar ningún registro auxiliar:
multi $r1, $r3, -1
mult $r1, $r2, $r1
18
También tendremos instrucciones para controlar el flujo de ejecución, para gestionar entrada salida, etc. A lo largo del tema iremos viendo esas instrucciones según las vayamos necesitando.
Generación de código para expresiones.
Empezaremos por generar código para las expresiones. Asumiremos que para gestionar el código disponemos de una función auxiliar, emite. Esta función recibe una cadena que representa una línea de código intermedio y hace lo necesario para que termine en el programa final; por ejemplo, puede escribirla en un fichero, almacenarla en una lista para pasarla a otro modulo, etc.
Expresiones aritméticas
Comenzamos el estudio por las expresiones aritméticas. Lo que tendremos que hacer es crear por cada tipo de nodo un método, genera Código, que genere el código para calcular la expresión y lo emita. Ese código dejar ‘a el resultado en un registro, cuyo nombre devolver ‘a genera Código como resultado.
Para reservar estos registros temporales, utilizaremos una función, reservaRegistro. En principio bastara con que esta función devuelva un registro distinto cada vez que se la llame. Cada nodo generara el código de la siguiente manera:
• Por cada uno de sus operandos, llamara al método correspondiente para que se evalúe la sub expresión.
• Si es necesario, reservara un registro para guardar su resultado.
• Emitirá las instrucciones necesarias para realizar el cálculo a partir de los operandos.
Con este esquema, la generación del código para una suma será:
Objeto Nodo Suma:
. . .
19
Método generaCódigo ()
izda:= i.generaCódigo ();
dcha:= d.generaCódigo ();
r =reserva Registro ();
emite (add r, izda, dcha);
devuelve r;
fin genera Código
. . .
fin Nodo Suma
En el caso de los operadores unarios, bastara con reservar un registro y hacer la correspondiente operación.
Ejercicio 1
[Escribe el método de generación de código para el operador unario de cambio de signo.]
La generación de código para constantes se limita a almacenar el valor en un registro:
Objeto Nodo Constante:
. . .
Método generaCódigo ()
r :=reserva Registro ();
emite (addi r, $zero, valor);
devuelve r;
20
fin generaCódigo
. . .
fin Nodo Constante
Para las variables, tendremos que distinguir según si son locales o globales y acceder a la dirección correspondiente. Las variables globales tendrán una dirección absoluta a la que accederemos utilizando el registro $zero como base y las variables locales y parámetros de las funciones tendrán una dirección relativa al registro $fp (frame pointer):
Objeto NodoAccesoVariable:
. . .
Método generaCódigo()
r :=reserva Registro();
if eslocal entonces
emite(lw r, dir($fp) );
si no
emite(lw r, dir($zero) );
fin si
devuelve r;
fin generaCódigo
. . .
fin NodoVariableGlobal
21
Observa que asumimos que las variables están en memoria y no permanentemente en registros. Esto no es óptimo, pero nos permite presentar un método de generación razonablemente sencillo.
Por ejemplo, vamos a traducir la expresión (a+2)*(b+c). Si suponemos que las variables están en las direcciones 1000, 1001 y 1002, respectivamente, el código que se generara es:
lw $r1, 1000($zero)
addi $r2, $zero, 2
add $r3, $r1, $r2
lw $r4, 1001($zero)
lw $r5, 1002($zero)
add $r6, $r4, $r5
mult $r7, $r3, $r6
Es bueno intentar reducir el número de registros utilizados. Por ejemplo, al llamar a una función, hay que guardar en la trama de activación todos los registros activos en ese punto. Por eso, será bueno intentar “reutilizar” los registros y tener el mínimo número de ellos ocupados. Si vamos a tener una fase de optimización, no hace falta ocuparse ahora de esta cuestión. Si no tenemos un optimizador, podemos utilizar una estrategia sencilla que reduce bastante el número de registros empleados. La idea es tener también una función liberaRegistro que marca un registro como disponible para una nueva llamada de reservaRegistro. Para implementarlas, podemos tener una lista de registros activos y otra de registros libres. La llamada a reservaRegistro devuelve un elemento de la lista de registros libres, si no está vacía, o crea un nuevo registro si lo está. La llamada a liberaRegistro mueve el registro a la lista de registros libres. La lista de registros activos se emplea, por ejemplo, para saber que registros hay que guardar en las llamadas a función.
22
Siguiendo esta idea, el no do de la suma sería:
Objeto NodoSuma:
. . .
Método generaCódigo()
izda:= i.generaCódigo();
dcha:= d.generaCódigo();
emite(add r, izda, dcha);
liberaRegistro(dcha);
devuelve izda;
fin generaCódigo
. . .
fin NodoSuma
Para (a+2)*(b+c) se generaría:
lw $r1, 1000($zero)
addi $r2, $zero, 2
add $r1, $r1, $r2
lw $r2, 1001($zero)
lw $r3, 1002($zero)
add $r2, $r2, $r3
mult $r1, $r1, $r2
En el caso de los unarios, directamente reutilizaremos el registro correspondiente al operando.
23
Actividad 4: Lenguaje prototipo aplicando las acciones construidas a la gramática.
La sintaxis de un lenguaje de programación describe la forma correcta en la cual las sentencias, expresiones y unidades de programa se deben escribir, mientras que la semántica denota el significado de esas sentencias, expresiones y unidades de programa. Por ejemplo la sintaxis de una sentencia Pascal if then es:
if <condición> then <sentencia>
La semántica de esta sentencia es que si el valor actual de la condición es verdadero, se ejecutará <sentencia>.
Describir sintaxis es más fácil que describir semántica, ya que existen notaciones aceptadas universalmente para la descripción de sintaxis y no así de semánticas.
En la definición de un lenguaje de programación la sintaxis usualmente se expresa en BNF (Backus- Naur Form) y la semántica está expresada en lenguaje natural (español, inglés, etc.).
BNF es un ejemplo de un metalenguaje, es decir, un lenguaje usado para definir otros lenguajes. Algol 60 fue el primer lenguaje que utilizó BNF para su descripción sintáctica.
Una gramática consiste de un conjunto de no-terminales, terminales y una serie de reglas de producción. Un no-terminal está definido en una regla de producción, mientras que un terminal es un símbolo del lenguaje que se está definiendo. En una regla de producción el no-terminal (que aparece en la parte izquierda) está definido en términos de una secuencia de no-terminales y terminales (que se encuentran en la parte derecha)
Ejemplo:
24
<dígito>::= 0|1|2|3|4|5|6|7|8|9
<letra>::= a|b|c……………|x|y|z
<identificador>::=<letra>|<identificador><dígito>|<identificador><letra>
En BNF, un no-terminal se escribe entre <>, el símbolo ::= significa “se define como” y el símbolo “|” significa “o”.
Estas reglas definen <dígitos> como uno de los símbolos 0, 1 al 9; <letra> como una letra minúscula e <identificador> se define como una única letra, un identificador seguido de una letra o un identificador seguido de un dígito.
Así, el identificador “ab1” puede ser derivado de <identificador>como sigue:
<identificador>
<identificador><dígito>
<identificador><letra>><dígito>
<letra><letra><dígito> a <letra><dígito> a b <dígito>
a b 1
En cada etapa, el no-terminal de más a la izquierda es reemplazado por la parte derecha de una de sus reglas de producción, la secuencia de terminales y no-terminales producidos en cada etapa en una derivación se conoce como formas sentenciales. La forma sentencia final (que ya no contiene símbolos no-terminales) se conoce como una sentencia.
La estructura de una derivación se muestra mejor en un árbol de derivación. El árbol de derivación que muestra cómo ab1 de <identificador> es:
<identificador>|<identificador> <dígito>| |
25
<identificador><letra> 1| |<letra>b| a
Un lenguaje de programación completo se define comenzando con un símbolo no- terminal tal como <programa>, conocido como start symbol (símbolo inicial) y desde el cual todos los posibles programas pueden ser derivados. En la práctica, los árboles de derivación se crean de dos formas posibles. En un esquema top-down, la sentencia requerida se deriva del símbolo inicial tal cual como hicimos en el ejemplo (ab1). En un esquema bottom-up, el punto de partida es la sentencia requerida, la cual es reducida al símbolo inicial reemplazando las partes derechas por sus correspondientes partes izquierdas de las reglas de producción. Ambos esquemas se utilizan en la fase de análisis sintáctico de muchos compiladores.
Una gramática que define un lenguaje de programación tiene un número finito de reglas de producción, pero como las reglas de producción contienen recursión, es posible generar infinitos programas posibles.
Ejemplo:
<ident>::=<ident><dígito>
Cuando se incluye recursión en las reglas de producción hay que tener cuidado. Hay que asegurarse que la recursión termine. Una regla de producción tal como la anterior se dice que es recursiva a izquierda. Existen definiciones similares pero recursivas a derecha.
Ejemplo: Gramática para una sentencia de asignación
<asig>::= <id>:=<exp>
<id>::= A| B| C |D (también podría ser la definición de identificador anterior)
<exp>::= <exp> + <exp>
| <exp> * <exp>
| <id>
26
¿Qué pasa con esta gramática? ¿Es correcta? Veamos el árbol de derivación de la siguiente sentencia.
A:= B + C * D
Árbol 1
<asig>
|<id> := <exp>| |A <exp> + <exp>| |<id> <exp> * <exp>| | |B <id> <id>| |C D
Árbol 2
<asig>|<id> := <exp>| |A <exp> * <exp>| |<exp> + <exp> <id>| | |<id> <id> D| |B C
En general la ambigüedad sintáctica de las estructuras de un lenguaje es un problema, debido a que los compiladores basan la semántica de esas estructuras en su estructura sintáctica. Si una estructura del lenguaje tiene más de un árbol de
27
derivación, entonces, el significado de la estructura no podría determinarse unívocamente.
¿Cómo desambiguamos la gramática anterior?
<asig>::= <id> := <exp>
<id>::= A| B| C| D
<exp>::= <exp> + <término> | <término>
<término>::= <término> * <factor> | <factor>
En general puede ser una declaración o un conjunto separados por “;”. Y una declaración puede ser un identificador o un conjunto separados por “,”:
<declaración>::= var <lista-de-dcls>
<lista-de-dcls>::= <unaDeclaración> | <unaDeclaración>; <lista –de-dcls>
<unaDeclaración>::= <lista-de-ident> : <tipo>
<lista-de-ident>::= <ident> |<ident>, <lista-de-ident>
<ident>::= Se definió en la teoría!!!
<tipo>::= ………..no hacerlo, en este caso para acá
Las anteriores son sólo reglas de producción de la gramática. La gramática se compone además de reglas de:
Dónde:
G={ N, T, P, S}
28
T = {var, “,” , “;” , : , etc……}
N = { <declaración>, <lista –de-dcls>, <unaDeclaración>, <lista-de-ident>,
<tipo>, <ident>}
P = { <declaración>::= var <lista-de-dcls>
<lista-de-dcls>::= <unaDeclaración> | <unaDeclaración>; <lista-de-dcls>
<unaDeclaración>::= <lista-de-ident> : <tipo>
<lista-de-ident>::= <ident> |<ident>, <lista-de-ident>
<ident>::= Se definió en la teoría!!!
<tipo>::= ………..no hacerlo, en este caso para acá
}
S = {<declaración>}
Prototipo de gramatica
abstract class Comm {
public Object visita(Visitor v);
}
class While extends Comm { public Expr e;
public Comm c;
29
public Object visita(Visitor v) { return v.visitaWhile(this);
}
}
class If extends Comm { public Expr e;
public Comm c1, c2;
public Object visita(Visitor v) { return v.visitaIf (this);
}
}
class Seq extends Comm { public Comm c1, c2;
public Object visita(Visitor v) { return v.visitaSeq(this);
}
}
class Assign extends Comm { public String name;
public Expr e;
public Object visita(Visitor v) { return v.visitaAssign(this);
}
}
class Skip extends Comm { Object visita(Visitor v) {
30
return v.visitaSkip(this);
}
}
En el caso de las expresiones, se realiza el mismo esquema.
abstract class Expr {
public abstract Object visita(Visitor v);
}
class BinOp extends Expr { public Operator op; public Expr e1,e2;
public Object visita(Visitor v) { return v.visitaBinOp(this);
}
}
class Var extends Expr { public String name;
public Object visita(Visitor v) { return v.visitaVar(this);
}
}
class Const extends Expr { public int n;
public Object visita(Visitor v) { return v.visitaConst(this);
31
}
}
Se define una clase abstracta Visitor cuyas subclases representarán posibles recorridos. La clase incluye métodos del tipo Object visitaX(X n) para cada tipo de nodo X del árbol sintáctico.
abstract class Visitor { Object visitaWhile(While w); Object visitaIf(If i); Object visitaSeq(Seq s);
Object visitaAssign(Assign a); Object visitaSkip(Skip s); Object visitaBinOp(BinOp b); Object visitaVar(Var v); Object visitaConst(Const c);
}
A continuación se define como un posible recorrido del árbol sintáctico y por tanto, una subclase de Visitor.
class Interp extends Visitor {
// Aquí se pueden definir los elementos del contexto (Tabla, Memoria, etc.) Object visitaWhile(While w) {
for (;;) {
BValue v = (BValue) w.e.visita(this); if (!v.b) break;
w.c.visita(this);
}
}
Object visitaIf(If i){
BValue v = (BValue) i.e.visita(this); if (!v.b) i.c1.visita(this);
else i.c2.visita(this);
}
32
Object visitaSeq(Seq s){ s.c1.visita(this); s.c2.visita(this);
}
Object visitaAssign(Assign a) { Value v = (Value) a.e.visita(this); update(a.name,v);
}
Object visitaSkip(Skip s) {
}
Object visitaBinOp(BinOp b) {
Value v1 = (Value) b.e1.visita(this); Value v2 = (Value) b.e2.visita(this); return (b.op.apply(v1,v2));
}
Object visitaVar(Var v){ return lookup(v.name);
}
Object visitaConst(Const c) { return c.n;
}
// Función de ejecución de órdenes Object exec(Comm c) {
c.visita(this);
}
}
33
Actividad 5: Tres programas tipo, usando la gramática definida de su lenguaje de programación.
Programación basada en prototipos es un estilo de programación orientada a objetos en el cual los objetos no son creados mediante la instanciación de clases sino mediante la clonación de otros objetos o mediante la escritura de código por parte del programador. De esta forma los objetos ya existentes pueden servir de prototipos para los que el programador necesite crear.
El original (y el más canónico) ejemplo de lenguaje prototipado es el lenguaje Self, desarrollado por David Ungar y Randall Smith.
Sin embargo el paradigma sin clases está comenzando a popularizarse y ya ha sido implementado en lenguajes de programación como JavaScript, Cecil, NewtonScript, IO,MOO, REBOL, Squeak.
Analógicamente.
Un ejemplo de instancia en un lenguaje de programación visual, sería tomar o arrastrar un objeto de la barra de herramientas o de la lista de librerías y colocarlo en el escritorio o escenario de trabajo (estamos creando una instancia de ese objeto, una copia). Si arrastramos 10 botones al entorno visual de trabajo, estamos creando una instancia del botón original, si a cada botón le cambiamos el nombre, tendremos 10 botones que heredan las mismas propiedades y métodos del objeto original. Tenemos como resultado que con un solo botón hicimos 10 y nuestro archivo pesara como si tuviese uno solo.
De esta forma, partiendo de lo que conforma a un objeto original (propiedades y métodos) se reutilizan sus funciones creando una instancia del mismo en distintas partes del programa donde se necesite. Si el objeto original cambia o le es agregado algún nuevo atributo, las instancias lo heredaran puesto que son una copia del objeto original.
Comparación del modelo basado en clases
En lenguajes basados en clases los objetos pueden ser de dos tipos generales, las clases y las instancias. Las clases definen la disposición y la funcionalidad básicas de los objetos, y las instancias son objetos "utilizables" basados en los patrones de una clase particular. En este modelo, las clases actúan como colecciones de comportamiento (métodos) y estructuras que son iguales para todas las instancias, mientras que las instancias llevan los datos de los objetos. La
34
distinción del papel se basa así sobre todo en una distinción entre la estructura y el comportamiento en un lado, y el estado en el otro.
Los entusiastas de la programación basada en prototipos a menudo argumentan que los lenguajes basados en clases animan un modelo del desarrollo que se centra primero en la taxonomía y las relaciones entre las clases. En cambio, la programación basada en prototipos intenta animar al programador que se centre en el comportamiento de un cierto sistema de ejemplos y después de clasificar estos objetos en objetos arquetipos que se utilizan más adelante en una manera similar a las clases. Como tal, muchos sistemas basados en prototipos animan la alteración de prototipos durante tiempo de ejecución, mientras que solamente muy pocos sistemas orientados a objeto, basados en clase (como el primer sistema orientado a los objetos dinámicos, Smalltalk) permiten que las clases sean alteradas durante la ejecución de un programa.
Mientras que la amplia mayoría de sistemas basados en prototipos se refieren a lenguajes de programación interpretados y de tipos de datos dinámicos, es importante precisar que los sistemas de tipos de datos estáticos son técnicamente factibles. El lenguaje de programación de Omega que es basado en prototipos es un ejemplo de tal sistema, aunque según el Web site de Omega, Omega no es exclusivamente de tipos de datos estáticos, pero su "compilador puede elegir utilizar el tipo de dato estático donde es posible esto y puede mejorar la eficacia del programa.”
Gramáticas formales
Las gramáticas de todos los lenguajes humanos conocidos tienen una cosa en común: están organizadas por frases. Digamos a “grosso modo” que una frase es una unidad de lenguaje. A su vez una frase para que sea sintácticamente correcta debe estar formada por sujeto y predicado; el predicado debe incluir un verbo; si el verbo es transitivo debe llevar un objeto directo, etc….. Tratamos de expresarlo con símbolos:
<frase>::= <sujeto><predicado><sujeto>::= juan / antonio / maría / pepa
<predicado>::= <verbo transitivo><objeto directo>
<predicado>::= <verbo intransitivo>
<verbo transitivo>::= ama / lava / peina / adora
<objeto directo>::= paula / antonio / sultán
<verbo intransitivo>::= corre / salta / camina
Si partimos de <frase> y seguimos estas ‘reglas’ podemos obtener
35
<frase> → <sujeto><predicado> → maría<predicado> →
→ maría<verbo transitivo><objeto directo> →
→ maría ama <objeto directo> → maría ama a sultán.
Hemos construido una frase utilizando sólo las reglas anteriores. Decimos que ‘maría ama a sultán’ es una frase de la gramática. De modo análogo
<frase> → <sujeto><predicado> → juan<predicado> →→ juan<verbo intransitivo> → juan camina
Así ‘juan camina’ es otra frase de nuestra gramática. Queremos hacer una definición formal de gramática, para ello vamos a tratar de abstraer lo principal de lo que en el lenguaje normal llamamos gramática. Observamos que hay dos tipos de componentes: los que escribimos entre corchetes <>y las que no. Llamaremos a esos dos tipos de objetos no terminales y terminales, respectivamente. Obsérvese que una verdadera frase está escrita sólo con terminales y los no terminales se usan sólo en pasos intermedios, para construir con sintaxis correcta. Lo fundamental de la gramática son las ‘reglas’ que nos van a permitir formar frases correctas. Sólo las frases que formemos de acuerdo a esas reglas serán frases correctas del lenguaje de nuestra gramática.
Las gramáticas son mecanismos generadores de lenguajes, es decir, nos dicen cómo podemos obtener o construir palabras de un determinado lenguaje.
Una gramática es una cuádrupla G = (VN, VT, S, P) donde:
VT : Es el alfabeto de símbolos terminales.
V N: Es el alfabeto de símbolos no terminales o variables, de forma que debe ser VN ∩ VT =∅ y denotamos con V al alfabeto total de la gramática, esto es, V = VN∪ VT.
S: Es el símbolo inicial y se cumple que S ∈ VN.
P: Es un conjunto finito de reglas de producción.
A veces se utiliza una notación especial para describir gramáticas llamada notación BNF (Backus-Naus-Form ). En la notación BNF los símbolos no terminales o variables son encerrados entre ángulos y utilizaremos el símbolo ::= para las producciones, en lugar de →. Por ejemplo, la producción S → aSa se representa en BNF como S ::= a S a. Tenemos también la notación BNF- extendida que incluye además los símbolos [] y {} para indicar elementos opcionales y repeticiones, respectivamente.
36
Tenemos un lenguaje de programación cuyas dos primeras reglas de producción para definir su sintaxis son:
Esto viene a decir que un programa se compone de una cabecera opcional, seguido de la palabra clave “begin”, a continuación una lista de sentencias (debe haber al menos una sentencia) y finaliza con la palabra clave “end”. Podemos transformar las producciones anteriores para especificarlas, según la notación que nosotros hemos introducido (estándar)
Programa 1:
Programa que acepta expresiones, para realizar este programa se utiliza la siguiente gramática.
E –> E+T | T
T –> T*R | R
R –> R-W | W
W –> W/F | F
E –> (E) | 0…9
Código:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
using namespace std;
char entrada[50];
char *ca=entrada; /*caracter actual*/
37
//declaracion de funcionees
void E();
void Eprima();
void T();
void Tprima();
void R();
void Rprima();
void W();
void Wprima();
void F();
void Aceptado();
int main(){
cout<<"ingrese la expresion: ";
gets(entrada);
E();
cout<<"\nLa expresion si es valida\n"<<endl;
system("pause");
}
void E()// *E –> TE’
{
T();
Eprima();
}
38
void Eprima() // E’ –> +TE’|vacío
{
if(*ca=='+')
{
Aceptado();
ca++;
T();
Eprima();
}
//vacío
}
void T() // T –> RT’
{
R();
Tprima();
}
void Tprima() // T’ –> *RT’|vacío
{
if(*ca=='*')
{
Aceptado();
ca++;
R();
39
Tprima();
}
//vacío
}
void R() // R –> WR’
{
W();
Rprima();
}
void Rprima() // R’ –> -WR’|vacío
{
if(*ca=='-')
{
Aceptado();
ca++;
W();
Rprima();
}
//vacío
}
void W() // W –> FW’
{
F();
Wprima();
40
}
void Wprima() // W’ –> /FW’|vacío
{
if(*ca=='/')
{
Aceptado();
ca++;
F();
Wprima();
}
//vacío
}
void F() // F –> (E)|0…9
{
if(isdigit(*ca))
{
Aceptado();
ca++;
}
else if (*ca=='(')
{
Aceptado();
ca++;
E();
41
if (*ca==')')
{
Aceptado();
ca++;
}
else
{
cout<<"se espera parentesis derecho u operador :"<<endl;
system("pause");
exit(0);
}
}
else
{
cout<<"se espera digito o paréntesis izquierdo:"<<endl;
system("pause");
exit(0);
}
}
void Aceptado() //esta función avisa que el caracter actual es aceptado
{
cout<<" caracter ' "<<*ca<<" ' aceptado"<<endl;
}
42
Programa 2:
#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#include<string>
#define TAM_BUFFER 100
using namespace std;
classLexico
{
char *nombreFichero;
FILE* entrada;
int n1;
int traza;
char buffer[TAM_BUFFER];
int pBuffer;
43
public:
Lexico(char *unNombreFichero, int una_traza=0);
~Lexico(void);
char siguienteToken(void);
void devuelveToken(char toke);
int lineaActual(void){return n1;};
int existeTraza(void){if(traza)return 1; else return 0;}
};
Lexico::Lexico(char *unNombreFichero, int una_traza)
{
entrada=fopen(unNombreFichero, "rt");
if((entrada==NULL))
{
cout<<"No se puede abrir el archivo"<<endl;
system("pause");
exit(-2);
}
if(una_traza) traza=1;
else traza = 0;
n1=1;
pBuffer=0;
}
Lexico::~Lexico()
{
fclose(entrada);
44
}
char Lexico::siguienteToken(void)
{
char car;
while((car=((pBuffer>0) ? buffer[-pBuffer]:getc(entrada)))!=EOF)
{
if(car==' ') continue;
if(car=='\n'){++n1; continue;}
break;
}
if(traza) cout<<"ANALIZADOR LEXICO: Lee el token : "<<car<<endl;
switch(car)
{
case'M':
case'R':
case'W':
case'=':
case'(':
case')':
case';':
case'}':
case'{':
case'.':
case'+':
case'*':
45
case'-':
case'/':
case'%':
return(car);
}
if(islower(car))return(car);
else if(isdigit(car)) return(car);
else
{
cout<<"Error Lexico: Token Desconocido"<<endl; system("pause");
exit(-4);
}
return(car);
}
void Lexico::devuelveToken(char token)
{
if(pBuffer>TAM_BUFFER)
{
cout<<"ERROR: Desbordamiento del buffer del analizador lexico"<<endl; system("pause");
exit(-5); } else { buffer[pBuffer++]=token; if(existeTraza()) cout<<"ANALIZADOR LEXICO: Recibe en buffer el token"<<token<<endl; system("pause"); } }
46
int main()
{
int traza;
char token;
Lexicoobj("ejemplo_minidev.txt",1);
if(obj.existeTraza()) cout<<"INICIO DE ANALISIS"<<endl;
while((token=obj.siguienteToken() )!='}') cout<<token<<endl;
system("pause");
return 0;
}
Programa 3:
void inapre(){ system("cls"); int cy=0,rt=0,p=0,uui=0;
47
for(int g=0;g<=n;g++){
if(Frase[g] == ')' || Frase[g] == '+' || Frase[g] == '-' ||Frase[g] == '/' ||Frase[g] == '*'||Frase[g] == '('||Frase[g] == '^'){{
//cout<<endl<<prioridad(Frase[g])<<" "<<prioridad(pi[cy-1])<<endl<<endl; //cout<<Frase[g]<<" "<<pi[cy-1]<<endl; if( (Frase[g] != pi[cy-1]) && ( prioridad(Frase[g]) == prioridad(pi[cy-1]) ) && cy != 0 && Frase[g] != '(' && Frase[g] != ')'&& pi[cy-1] != '(' && pi[cy-1] != ')'){ pref[rt] = pi[cy-1]; cout<<pref[rt]<<" prio"<<endl; cout<<pi<<" SP "<<endl; rt++; pi[cy-1] = Frase[g]; cy--; }
if(Frase[g] == ')' ){ for(int jj=p+1;jj<cy;jj++){ pref[rt] = pi[jj]; cout<<pref[rt]<<" SS "<<endl; rt++; } for(int hrh = p;hrh<=cy;hrh++){ pi[hrh] = ' '; } cy = p; }
if(Frase[g] == '('){ p = cy; } if(Frase[g] != ')'){ pi[cy] = Frase[g]; cout<<pi<<" S "<<endl; cy++; }
}
48
}else if(Frase[g] == '9' ||Frase[g] == '8' ||Frase[g] == '1' ||Frase[g] == '2' ||Frase[g] == '3' ||Frase[g] == '4' ||Frase[g] == '6' || Frase[g] == '5' || Frase[g] == '7'){ pref[rt] = Frase[g]; cout<<pref[rt]<<" N "<<endl; rt++;
}else if(g==n){ int t=cy-1; if(pi[0] == ')'){t=1;} for(t;t>=0;t--){ pref[rt] = pi[t]; cout<<pref[rt]<<" F"<<endl; rt++; } } cout<<endl<<pref<<endl<<endl; }// TERMINO DE FOR
cout<<pref<<endl; /* strcpy(Volt,pref); cout<<Volt<<"frase1"<<endl; uui = n-1; for(int xx=0;xx<n;xx++,uui--){ cout<<xx<<" "<<uui<<endl; pref[xx] = Volt[uui]; cout<<endl<<Volt[xx]<<" "<<Frase[uui]; } cout<<endl<<pref<<"frase2"<<endl; cout<<Volt<<"volt"<<endl;
cout<<pref<<endl;*/ system("pause"); }//TERMINO DE INAPRE
Conversión de infijo a postfijo y de infijo a prefijo:
49
Introducción de sentencia.
Infijo a postfijo:
50
Infijo a prefijo:
Actividad 6: Reporte del avance por equipo del proyecto final consistente en desarrollar software de base: traductor, intérprete o compilador.
51
void CodInterIf(){//Codigo intermedio system("cls"); char fu[100]; int guu=0; int qww=0; int cyu=0; char ins[100]; for(int jn=0;jn<=n;jn++){
if(Frase[jn] == '(' && Frase[jn+1] == ')'){ cout<<"Debe ingresar una condicion"<<endl;}else{//FIN IF 1 if(Frase[jn] == 'I' && Frase[jn+1] == 'f' && Frase[jn+2] == '('){//IF 2
for(int gu = (jn+3) ; Frase[gu] != ')' ; gu++,guu++){ fu[guu] = Frase[gu];}
cyu++;} //FIN IF 2
}//FIN ELSE if(Frase[jn] == ')' ){//FIN IF3 for(int qw = (jn+2) ; Frase[qw] != '}' ;qw++,qww++){//FORins[qww] = Frase[qw]; }//FIN FOR
}//FIN IF 3
}//FIN FOR
cout<<Frase<<endl; if(cyu>0){ for(int mm=0;mm<guu;mm++){ cout<<fu[mm]; } cout<<endl;cout<<"Salta Si falso Etiqueta1"<<endl;for(int mm=0;mm<qww;mm++){ cout<<ins[mm]; } cout<<endl; cout<<"Etiqueta1:"<<endl;
52
cout<<"END"<<endl;
} system("pause"); } //Fin codigo intermedio
void CodInterFor(){//Codigo intermedio For system("cls"); int cyf=0; int pc=0; int qw2=0,qw1=0; char pcl[10]; char ins1[10]; for(int jn=0;jn<=n;jn++){if(Frase[jn] == '(' && Frase[jn+1] == ')'){ cout<<"Debe ingresar una condicion"<<endl; }else{//FIN IF 1 if(Frase[jn] == 'F' && Frase[jn+1] == 'o' && Frase[jn+2] == 'r' && Frase[jn+3] == '('){//IF 2
cyf++; }
} //FIN IF 2
if(Frase[jn] == ';'){ pc++; } if(pc == 1 && Frase[jn] == ';'){ for(int qw = jn+1; Frase[qw] != ';' ;qw++,qw2++){ pcl[qw2] = Frase[qw];} }
if(Frase[jn] == ')' ){//FIN IF3for(int qw = (jn+2) ; Frase[qw] != '}' ;qw++,qw1++){//FORins1[qw1] = Frase[qw]; }//FIN FOR
}//FIN IF 3
}//FIN FOR
cout<<Frase<<endl;
53
if(cyf > 0 && pc == 2){
cout<<"Etiqueta1:"<<endl; for(int mm=0;mm<qw2;mm++){ cout<<pcl[mm]; } cout<<endl;cout<<"Salta Si falso Etiqueta2"<<endl;for(int mm=0;mm<qw1;mm++){ cout<<ins1[mm]; } cout<<endl;cout<<"Salta Etiqueta1"<<endl; cout<<"Etiqueta2:"<<endl;cout<<"END"<<endl;
} system("pause");}//Fin codigo intermedio for
Ventajas: dentro de las ventajas de la generación de código intermedio se encuentran que: permite abstraer la máquina, separar operaciones de alto nivel de su implementación a bajo nivel, permite la reutilización de los front-ends y back-ends; y permite optimizaciones generales.
Desventajas: implica una pasada más para el compilador (no se puede utilizar el modelo de una pasada), dificulta llevar a cabo optimizaciones específicas de la arquitectura destino; y suele ser ortogonal a la máquina destino, la traducción a una arquitectura específica será más larga e ineficiente.
Tipos de código intermedio.
54
AST (AbstractSyntaxTrees): forma condensada de árboles de análisis, con sólo nodos semánticos y sin nodos para símbolos terminales (se supone que el programa es sintácticamente correcto).
DAG (DirectedAcyclicGraphs): árboles sintácticos concisos.
TAC (Three-AddressCode): secuencia de instrucciones de la forma: operando, operador, operando, requieren varias instrucciones y permite la reorganización de código, manejo de ensamblador, utiliza direcciones de memoria y nemotécnicos, pueden ser instrucciones de 2, 3 o 4 por cada operación.
Se genera una condición donde este nos indica los identificadores, palabras reservadas y operadores que este analiza este es posible, una vez que realiza la identificación de tokens este procede a generar lo que es el código intermedio, este lo hace posible tomando en cuenta una función donde desglosa su árbol sintáctico.
55
56
Como se observa en la siguiente imagen además de generar una operación y mostrarnos los tokens de dicha operación este nos genera el código intermedio y a su vez nos muestra el ejemplo de nuestra operación en lo que seria el lenguaje ensamblador, para ello necesitamos lo que es la generación de código intermedio y análisis léxico y sintáctico, para poder llevar a cabo la Graficación de nuestro árbol sintáctico.
57
Anexo
Para llevar a cabo la función del analizador tomamos en cuenta los que son los caracteres del código asccii esto más que nada es para tener un control de los token’s y así mismo poder identificarlos más fácilmente cuando se realice su compilación o ejecución. El siguiente código realiza el trabajo de la imagen anterior, identifica cada token y este los va almacenando de manera que los compare con nuestro alfabeto.
Public Sub analizar()
token = "" Dim estadios As Integer = 0 Dim numtoken As Integer Dim arr() As String = RichTextBox1.Text.Split(ChrW(10))
Dim nn, mm As Integer For nn = 0 To arr.Length - 1 For mm = 0 To Len(arr(nn)) - 1
Dim c, d As String
c = Asc(arr(nn).Chars(mm))
If (estadios = 0) Then estadios = estado(c)
End If
Try d = Asc(arr(nn).Chars(mm + 1)) Catch d = -4
End Try
sig = d
Select Case estadios
Case 1 token = token + arr(nn).Chars(mm) If (sig > 64 And sig < 92) Or (sig > 96 And sig < 123) Or (sig < 58 And sig > 47) Then
58
estadios = 1 Else estadios = 0 numtoken = 1 End If
Case 2 token = token + arr(nn).Chars(mm) If (sig < 58 And sig > 47) Then estadios = 2ElseIf (sig = 46) Then
estadios = 3 Else estadios = 0numtoken = 2 End If
Case 3 token = token + arr(nn).Chars(mm) If (sig < 58 And sig > 47) Then estadios = 4 Else estadios = -1 End If Case 4 token = token + arr(nn).Chars(mm) If (sig < 58 And sig > 47) Then estadios = 4 Else estadios = 0 numtoken = 3 End If
Case 100 estadios = -2
Case -1 token = token + arr(nn).Chars(mm) numtoken = 10 estadios = 0
Case 300 token = token + arr(nn).Chars(mm) numtoken = 20 estadios = 0 Case 400
59
token = token + arr(nn).Chars(mm) numtoken = 40 estadios = 0 Case 500 token = token + arr(nn).Chars(mm) numtoken = 50 estadios = 0 Case 501 token = token + arr(nn).Chars(mm) numtoken = 60 estadios = 0 Case 502 token = token + arr(nn).Chars(mm) numtoken = 70 estadios = 0 Case 503 token = token + arr(nn).Chars(mm) numtoken = 80 estadios = 0 Case 504 token = token + arr(nn).Chars(mm) numtoken = 90 estadios = 0
End Select
If estadios = 0 Then numerotoken.Add(numtoken) lexema.Add(token)token = ""
ElseIf estadios = -2 Then estadios = 0
ElseIf estadios = -1 Then numerotoken.Add(10) lexema.Add(token) token = ""
ElseIf (sig = -3) Then If estadios <> 0 Then numerotoken.Add(-1)
60
lexema.Add(token)
End If End If
Next
Next
For ii = 0 To numerotoken.Count - 1
If (numerotoken(ii) = 1) Then If lexema(ii) = palabras_reservadas(1) Or lexema(ii) = palabras_reservadas(2) Or lexema(ii) = palabras_reservadas(3) _ Or lexema(ii) = palabras_reservadas(4) Or lexema(ii) = palabras_reservadas(5) Or lexema(ii) = palabras_reservadas(6) _ Or lexema(ii) = palabras_reservadas(7) Or lexema(ii) = palabras_reservadas(8) Or lexema(ii) = palabras_reservadas(9) _ Or lexema(ii) = palabras_reservadas(10) Or lexema(ii) = palabras_reservadas(11) Or lexema(ii) = palabras_reservadas(12) _ Or lexema(ii) = palabras_reservadas(13) Or lexema(ii) = palabras_reservadas(14) Or lexema(ii) = palabras_reservadas(15) _ Or lexema(ii) = palabras_reservadas(16) Or lexema(ii) = palabras_reservadas(17) Or lexema(ii) = palabras_reservadas(18) _ Or lexema(ii) = palabras_reservadas(19) Or lexema(ii) = palabras_reservadas(20) Or lexema(ii) = palabras_reservadas(21) _ Or lexema(ii) = palabras_reservadas(22) Or lexema(ii) = palabras_reservadas(23) Or lexema(ii) = palabras_reservadas(24) _ Or lexema(ii) = palabras_reservadas(25) Or lexema(ii) = palabras_reservadas(26) Or lexema(ii) = palabras_reservadas(27) _ Or lexema(ii) = palabras_reservadas(28) Or lexema(ii) = palabras_reservadas(29) Or lexema(ii) = palabras_reservadas(30) Then reserva(cont1) = lexema(ii) cont1 = cont1 + 1
ListBox2.Items.Add("Palabra Reservada:" & " " & lexema(ii))Else reserva(cont1) = lexema(ii) cont1 = cont1 + 1 ListBox2.Items.Add("identificador" & " " & lexema(ii)) End If
ElseIf (numerotoken(ii) = 10) And (lexema(ii) <> ChrW(10)) And (lexema(ii) <> ChrW(13)) Then
61
ListBox2.Items.Add("Operador Matematico" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 40) And (lexema(ii) <> ChrW(10)) And (lexema(ii) <> ChrW(13)) ThenListBox2.Items.Add("Operador Comparativo" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 90) Then If lexema(ii) = "(" Then parenta(cont2) = lexema(ii) cont2 = cont2 + 1 cont5 = cont5 + 1 End If If cont5 = 0 Then If lexema(ii) = ")" Then parenta(cont2) = lexema(ii)
End If Else If lexema(ii) = ")" Then parentc(cont3) = lexema(ii) cont3 = cont3 + 1
End If
End IfListBox2.Items.Add("Comp. De Operacion:" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 80) Then ListBox2.Items.Add("Operador Exponencial" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 60) Then ListBox2.Items.Add("Operador Booleano" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 70) Then ListBox2.Items.Add("Corte" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 50) Then ListBox2.Items.Add("Comentario" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 20) And (lexema(ii) <> ChrW(10)) And (lexema(ii) <> ChrW(13)) Then ListBox1.Items.Add("Error" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 3) Then num(cont4) = lexema(ii) cont4 = cont4 + 1 cont6 = 1 'ListBox2.Items.Add("Decimal" & " " & lexema(ii))
ElseIf (numerotoken(ii) = 2) Then
62
If cont6 = 0 Then num(cont4) = lexema(ii) cont4 = cont4 + 1 cont6 = 0
End If 'ListBox2.Items.Add("Entero" & " " & lexema(ii))
End If
Next
'For i = 0 To cont1'If reserva(i) = palabras_reservadas(2) Or reserva(i) = palabras_reservadas(3) Or reserva(i) = palabras_reservadas(4) _ ' Or reserva(i) = palabras_reservadas(5) Or reserva(i) = palabras_reservadas(6) Or reserva(i) = palabras_reservadas(11) _' And parenta(i) = caracteres(1) And parentc(i) = caracteres(2) Then 'ListBox2.Items.Add("Funcion" & " " & reserva(i) & parenta(i) & num(i) & parentc(i)) 'Else 'ListBox1.Items.Add("Error" & " " & reserva(i) & parenta(i) & num(i) & parentc(i))'End If 'Next
End Sub
63
Conclusion
Esta fase del compilador no es en realidad una parte separada del compilador, la mayoría de los compiladores generan código como parte del proceso de análisis sintáctico, esto es debido a que requieren del árbol de sintaxis y si este no va a ser construido físicamente, entonces deberá acompañar al analizador sintáctico al barrer el árbol implícito. En lugar de generar código ensamblador directamente, los compiladores generan un código intermedio que es más parecido al código ensamblador, las operaciones por ejemplo nunca se hacen con más de dos operandos. Al no generarse código ensamblador el cual es dependiente de la computadora específica, sino código intermedio, se puede reutilizar la parte del compilador que genera código intermedio en otro compilador para una computadora con diferente procesador cambiando solamente el generador de código ensamblador al cual llamamos back-end, la desventaja obviamente es la
lentitud que esto conlleva.
La tarea de síntesis suele comenzar generando un código intermedio. El código intermedio no es el lenguaje de programación de ninguna máquina real, sino que corresponde a una máquina abstracta, que se debe de definir lo más general posible, de forma que sea posible traducir este código intermedio a cualquier máquina real. El objetivo del código intermedio es reducir el número de programas necesarios para construir traductores, y permitir más fácilmente la transportabilidad de unas máquinas a otras. Supóngase que se tienen n lenguajes, y se desea construir traductores entre ellos. Sería necesario construir n*(n-1) traductores.
Sin embargo si se construye un lenguaje intermedio, tan sólo son necesarios 2*n traductores.
Así por ejemplo un fabricante de compiladores puede construir un compilador para diferentes máquinas objeto con tan sólo cambiar las dos últimas fases de la tarea de síntesis.
64
ReferenciasGarcía-Beltrán, A. M. (s.f.). Métodos Informáticos . Mc Graw Hill.
J., D. (1991). La Biblia de TurboPascal. Madrid.: Anaya Multimedia.
Jaume. (22 de Enero de 2010.). Ingeniera Informática, Procesadores de Lenguaje. Obtenido de Ingeniera Informática, Procesadores de Lenguaje.: Generación de Código.
Joyanes. (1996). Fundamentos de programación, Algoritmos y Estructuras de Datos, Segunda edición. McGrawHill.
lawebdelprogramador. (s.f.). Transformar-notacion-infija-a-postfija. Obtenido de • http://www.lawebdelprogramador.com/foros/Dev-C/724318-Transformar-notacion-infija-a-postfija.html
pilas-listas-expresiones. (s.f.). monografias. Obtenido de http://www.monografias.com/trabajos44/pilas-listas-expresiones/pilas-listas-expresiones2.shtml#ixzz3nXiPGmfk
TurboPascal. (s.f.). Obtenido de Ed. Bellisco, 2ª edición, Madrid, 2002
65