martes, 6 de junio de 2017

ACTIVIDAD. Código encapsulado



¿Que es encapsulamiento?


El encapsulamiento, que tiene como objetivo hacer que lo que pase en el interior de cada objeto sea inaccesible desde el exterior, y que el comportamiento de otros objetos no pueda influir en él. Cada objeto sólo responde a ciertos mensajes y proporciona determinadas salidas.

Pero, en ciertas ocasiones, necesitaremos tener acceso a determinados miembros de un objeto de una clase desde otros objetos de clases diferentes, pero sin perder ese encapsulamiento para el resto del programa, es decir, manteniendo esos miembros como privados.

C++ proporciona un mecanismo para sortear el sistema de protección. En otros capítulos veremos la utilidad de esta técnica, pero de momento sólo explicaremos en qué consiste.



El modificador friend puede aplicarse a clases o funciones para inhibir el sistema de protección.

→Las relaciones de "amistad" entre clases son parecidas a las amistades entre personas:
→La amistad no puede transferirse, si A es amigo de B, y B es amigo de C, esto no implica que A sea amigo de C. (La famosa frase: "los amigos de mis amigos son mis amigos" es falsa en C++, y probablemente también en la vida real).
→La amistad no puede heredarse. Si A es amigo de B, y C es una clase derivada de B, A no es amigo de C. (Los hijos de mis amigos, no tienen por qué ser amigos míos. De nuevo, el símil es casi perfecto).
→La amistad no es simétrica. Si A es amigo de B, B no tiene por qué ser amigo de A. (En la vida real, una situación como esta hará peligrar la amistad de A con B, pero de nuevo me temo que en realidad se trata de una situación muy frecuente, y normalmente A no sabe que B no se considera su amigo).


Veamos un ejemplo muy sencillo:

#include <iostream>
using namespace std;
class A {
  public:
    A(int i=0) : a(i) {}
    void Ver() { cout << a << endl; }
  private:
    int a;
    friend void Ver(A); // "Ver" es amiga de la clase A
};
void Ver(A Xa) {
   // La función Ver puede acceder a miembros privados
   // de la clase A, ya que ha sido declarada "amiga" de A
   cout << Xa.a << endl;
}

int main() {
   A Na(20);
   Ver(Na);  // Ver el valor de Na.a
   Na.Ver(); // Equivalente a la anterior

   return 0;
}



Puedes comprobar lo que pasa si eliminas la línea donde se declara "EsMayor" como amiga de A.
Es necesario hacer una declaración previa de la clase A (forward) para que pueda referenciarse desde la clase B.Veremos que estas "amistades" son útiles cuando sobrecarguemos algunos operadores.


Actividad. Programa que integre sentencia IF-ELSE, Ciclo FOR, WHILE & SWITCH CASE

CODIGO FUENTE


#include <iostream>
#include <stdlib.h>

using namespace std;


int main(){
int opcion;


cout<<"MENU"<<endl;
cout<<"1.Contraseña"<<endl;
cout<<"2.Suma de series"<<endl;
cout<<"3.Contador Clasico"<<endl;


cout<<"Elige una opcion" ;
cin>>opcion;

switch (opcion) {
case 1:{

string password = "";
cout << "Ingrese la contrasenia: ";
cin >> password;
if(password == "nato9612")
{
cout << "Contrasenia correcta. Bienvenido";
}
else
{
cout << "Contrasenia incorrecta.Come popo";
}

break;
}
case 2:{
 int n,serie,suma;
cout<<"Ingrese el ultimo termino de la serie: ";
cin>>n;
suma=0;
serie=2;
cout<<"S = ";
while(serie<=n){
 cout<<serie<<",";
suma = suma + serie;
serie = serie + 2;
}
cout<<endl<<endl;
cout<<"La suma de la serie es = "<<suma<<endl;
   
}
break;

case 3:{
int i,num1;
cout<<"Ingrese el numero maximo"<<endl;
cin>>num1;
for(i=0; i<=num1; i++){
cout<<i<<endl;
}
break;
}
 }
system("PAUSE");
}

IF ELSE



WHILE



FOR


CASE




lunes, 8 de mayo de 2017

UNIDAD 4 ANÁLISIS SEMÁNTICO Y GENERACIÓN DE CÓDIGO INTERMEDIO.



4.1 Atributos


Los atributos, también llamados datos o variables miembro son porciones de información que un objeto posee o conoce de sí mismo. Una clase puede tener cualquier número de atributos o no tener ninguno. Se declaran con un identificador y el tipo de dato correspondiente. Además los atributos y tienen asociado un modificador que define su visibilidad

Los atributos son las características individuales que diferencian un objeto de otro y determinan su apariencia, estado u otras cualidades. Los atributos se guardan en variables denominadas de instancia, y cada objeto particular puede tener valores distintos para estas variables.

En general los atributos de un nodo reciben valor mediante la evaluación de las reglas semánticas asociadas a la producción usada en ese nodo propietario del atributo.

Los valores de los atributos sintetizados se calculan a partir de los valores de atributos de sus nodos hijos en el árbol de análisis sintáctico.

Los valores de los atributos heredados se calculan a partir de los valores de atributos de su nodo padre o sus nodos hermanos.


4.1.1 Atributos heredados



♞ Sirven para expresar la dependencia que hay entre una construcción del lenguaje de programación y su contexto.


♞ Siempre es posible reescribir una definición dirigida por sintaxis para que sea S-atribuida.


 ♞En ocasiones es más natural utilizar atributos heredados


4.1.2 Atributos Sintetizados




🚀 Los atributos sintetizados se utilizan ampliamente.


🚀 Si una definición dirigida por sintaxis tiene únicamente atributos sintetizados se dice que es S-atribuida.


🚀 El árbol de análisis sintáctico de una gramática S-atribuida puede decorarse mediante un recorrido en post orden.


Una de las principales propiedades de las clases es la herencia. Esta propiedad nos permite crear nuevas clases a partir de clases existentes, conservando las propiedades de la clase original y añadiendo otras nuevas.


↣ EJEMPLO:



La forma general de declarar clases derivadas es la siguiente:
      
      class <clase_derivada> : 
   [public|private] <base1> [,[public|private] <base2>] {};

 

En seguida vemos que para cada clase base podemos definir dos tipos de acceso, public o private. Si no se especifica ninguno de los dos, por defecto se asume que es private.

⏩public: los miembros heredados de la clase base conservan el tipo de acceso con que fueron declarados en ella.

private: todos los miembros heredados de la clase base pasan a ser miembros privados en la clase derivada.

De momento siempre declararemos las clases base como public, al menos hasta que veamos la utilidad de hacerlo como privadas.

Veamos un ejemplo sencillo basado en la idea del punto anterior:
               // Clase base Persona:
class Persona {
  public:
   Persona(char *n, int e);
   const char *LeerNombre(char *n) const;
   int LeerEdad() const;
   void CambiarNombre(const char *n);
   void CambiarEdad(int e);
   
  protected:
   char nombre[40];
   int edad;
};
 
// Clase derivada Empleado:
class Empleado : public Persona {
  public:
   Empleado(char *n, int e, float s);
   float LeerSalario() const;
   void CambiarSalario(const float s);
   
  protected:
   float salarioAnual;
};


Se puede ver que hemos declarado los datos miembros de nuestras clases como protected. En general es recomendable declarar siempre los datos de nuestras clases como privados, de ese modo no son accesibles desde el exterior de la clase y además, las posibles modificaciones de esos datos, en cuanto a tipo o tamaño, sólo requieren ajustes de los métodos de la propia clase.


Pero en el caso de estructuras jerárquicas de clases puede ser interesante que las clases derivadas tengan acceso a los datos miembros de las clases base. Usar el acceso protected nos permite que los datos sean inaccesibles desde el exterior de las clases, pero a la vez, permite que sean accesibles desde las clases derivadas.




Bibliografia:



http://c.conclase.net/curso/?cap=036

https://gramaticasformales.wordpress.com/category/traduccion-dirigida-por-sintaxis/

http://ocw.upm.es/lenguajes-y-sistemas-informaticos/programacion-en-java-i/Contenidos/LecturaObligatoria/12-tiposdeatributos.pdf

http://www.sc.ehu.es/sbweb/fisica/cursoJava/fundamentos/clases1/clases.htm

martes, 4 de abril de 2017

Generación de Código intermedio


 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. 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:
1. Se facilita la redestinació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.
• Se busca:  – transportabilidad  
                    – posibilidades de optimización
• Debe ser:  – abstracto
                      – sencillo
 • No se tiene en cuenta: – modos de direccionamiento
                                             – tamaños de datos
                                            – existencia de registros
                                            – eficiencia de cada operación
Código Intermedio
Ventajas
Desventajas
– 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 backends.
 – Permite optimizaciones generales.
– Implica una pasada más para el compilador (no se puede utilizar el modelo de una pasada, conceptualmente simple).
– Dificulta llevar a cabo optimizaciones especí- ficas de la arquitectura destino
 – 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
AST (Abstract Syntax Trees): 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 (Directed Acyclic Graphs): árboles sintácticos concisos
TAC (Three-Address Code): secuencia de instrucciones de la forma: – operador: aritmético / lógico – operandos/resultado: constantes, nombres, temporales.

TAC
 • Ensamblador general y simplificado para una máquina virtual: incluye etiquetas, instrucciones de flujo de control…
• Incluye referencias explícitas a las direcciones de los resultados intermedios (se les da nombre). • La utilización de nombres permite la reorganización (hasta cierto punto).
• Algunos compiladores generan este código como código final; se puede interpretar fácilmente (UCSD PCODE, Java)
Representaciones de TAC
Fuente: a := b * c + b * d;
Cuádruplas: el destino suele ser una temporal.
(*, b, c, t1)
 (*, b, d, t2)
(+, t1, t2, a)
Tripletas: sólo se representan los operandos
(1) (*, b, c)
(2) (*, b, d)
(3) (+, (1), (2))
(4) (:=, (3), a)
Tripletas Indirectas: + vector que indica el orden de ejecución de las instrucciones.
a := (b * c + b * d) * b * c;





Notaciones
 • Las notaciones sirven de base para expresar sentencias bien definidas.
• El uso más extendido de las notaciones sirve para expresar operaciones aritméticas.
• Las expresiones aritméticas se pueden expresar de tres formas distintas: infija, prefija y postfija.
• La diversidad de notaciones corresponde en que para algunos casos es más sencillo un tipo de notación.
• Las notaciones también dependen de cómo se recorrerá el árbol sintáctico, el cual puede ser en inorden, preorden o postorden; teniendo una relación de uno a uno con la notación de los operadores.
Infija
 • La notación infija es la más utilizada por los humanos por que es la más comprensible ya que ponen el operador entre los dos operandos. Por ejemplo a+b-5.
 • No existe una estructura simple para representar este tipo de notación en la computadora por esta razón se utilizan otras notaciones.
Postfija
• La notación postfija pone el operador al final de los dos operandos, por lo que la expresión queda: ab+5-
• La notación posftfija utiliza una estructura del tipo LIFO (Last In First Out) pila, la cual es la más utilizada para la implementación.
Prefija
• La notación prefija pone el operador primero que los dos operandos, por lo que la expresión anterior queda: +ab-5. Esto se representa con una estructura del tipo FIFO (First In First Out) o cola.
• Las estructuras FIFO son ampliamente utilizadas pero tienen problemas con el anidamiento aritmético.
Generación de código para las estructuras de control
 Una vez sabemos cómo generar código para las expresiones y asignaciones, vamos a ver cómo podemos generar el código de las estructuras de control. Hay dos estructuras que ya hemos visto implícitamente. Por un lado, la estructura de control más simple, la secuencia, consiste simplemente en escribir las distintas sentencias una detrás de otra. En cuanto a las subrutinas, bastara con crear el correspondiente prologo y epílogo según vimos en el tema anterior. Las sentencias de su cuerpo no tienen nada especial. A continuación veremos algunas de las estructuras más comunes de los lenguajes de programación imperativos. Otras estructuras se pueden escribir de forma similar.

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.

martes, 28 de marzo de 2017

UNIDAD III construcción de analizadores sintácticos


¿Qué es el analizador sintáctico?

 Es la fase del analizador que se encarga de chequear el texto de entrada en base a una gramática dada. Y en caso de que el programa de entrada sea válido, suministra el árbol sintáctico que lo reconoce. En teoría, se supone que la salida del analizador sintáctico es alguna representación del árbol sintáctico que reconoce la secuencia de tokens suministrada por el analizador léxico. En la práctica, el analizador sintáctico también hace:

 • Acceder a la tabla de símbolos (para hacer parte del trabajo del analizador semántico).

• Chequeo de tipos ( del analizador semántico).

• Generar código intermedio.

• Generar errores cuando se producen.

En definitiva, realiza casi todas las operaciones de la compilación. Este método de trabajo da lugar a los métodos de compilación dirigidos por sintaxis.


GRAMÁTICA AMBIGUA

En informática, a gramática reputa gramática ambigua si hay algo secuencia que puede generar en más que una forma (es decir, la secuencia tiene más de uno analice el árbol o más de uno derivación extrema izquierda). Una lengua esintrínsecamente ambiguo si puede ser generado solamente por gramáticas ambiguas.

Algunos lenguajes de programación tenga gramáticas ambiguas; en este caso, la información semántica es necesaria seleccionar previsto analiza de una construcción ambigua. Por ejemplo, adentro C el siguiente:


x * y;

se puede interpretar cualquiera como el declaración de un identificador y del tipo indicador a x, o como expresión en la cual xse multiplica cerca y y se desecha el resultado. Para elegir entre las dos interpretaciones posibles, a recopilador debe consultar su tabla de símbolo para descubrir si x se ha declarado como nombre del typedef que es visible a este punto.



 ELIMINACIÓN DE LA RECURSIVIDAD DE UNA GRAMÁTICA
La necesidad de este método, mencionada en la introducción del artículo, es que el análisis descendente
no puede manejar una gramática recursiva.
¿Qué es una gramática recursiva? R/ Es aquella en la que al menos un no terminal deriva en cadenas que
empiezan con el mismo no terminal, es decir existen producciones por ejemplo con el no terminal A, en
 las que van hacia una cadena que empieza con A.
¿Cómo resolver el problema?
R/ Se agrupan todas las producciones de A que existan, las que presentan recursividad y las que no, de la
siguiente manera:
A  Aα1 | Aα2 | … | Aαm | β1| β2 | … | βn
Las producciones de A que van a β son lógicamente las que no empiezan con el mismo símbolo no
terminal A. Después gymde hacer esto se sustituyen las producciones de A, primero las que empiezan con β,
por:
A β1A´ | β2A´ |… | βnA´
y después las producciones que tienen recursividad, por:
A´  α1A´ | α2A´ | … | αmA´| ε


 ARBOLES DE SINTAXIS ABSTRACTA

Los ASTs (Abstract Syntax Trees, o Árboles de Sintaxis Abstracta) sirven para
manejar la información semántica de un código. La forma más eficiente de manejar la
información proveniente de un lenguaje de programación es la forma arbórea; por éso la
estructura de datos elegida es un árbol. Además, construyendo ASTs a partir de un texto
podemos obviar mucha información irrelevante; si un AST se construye bien, no habrá
que tratar con símbolos de puntuación o azúcar sintáctica en el nivel semántico.
Al contrario que los flujos, una estructura en árbol puede especificar la relación
jerárquica entre los símbolos de una gramática.
Los ASTs pueden intervenir en varias fases del análisis: como producto del
análisis sintáctico, como elemento intermedio en sucesivos análisis semánticos y como
entrada para la generación de código

CONCLUSIONES
Ante la posibilidad, en la vida real, de encontrar gramáticas independientes del contexto, que no se
ajusten a los requerimientos de los analizadores sintácticos, es necesario conocer cuales son las formas de
lograr que esas gramáticas, lleguen a cumplir esos requerimientos.



martes, 21 de febrero de 2017

COMPILADORES: Análisis Semántico

Un compilador toma como su entrada un programa escrito en lenguaje fuente y produce un programa equivalente escrito lenguaje objeto. Un compilador se compone internamente de varias etapas o fases que ser realizan operaciones lógicas:

Análisis Semántico: Semántica de un lenguaje dar sentido a sus construcciones, como los tokens estructura y sintaxis. Semántica ayudan a interpretar los símbolos, sus tipos y sus relaciones con los demás. Análisis semántico los jueces si la sintaxis estructura construida en el programa de origen se deriva el significado o no.
CFG + semantic rules = Syntax Directed Definitions
Por ejemplo:
int a = value”;
No debe emitir un error léxico y la sintaxis en fase de análisis, ya que es léxico y estructuralmente correcto, pero se debe generar un error semántico como del tipo de asignación es diferente. Estas normas están definidas por la gramática de la lengua y evaluado en análisis semántico. Las siguientes tareas deben realizarse en análisis semántico:
  • Resolución de Ámbito
  • Comprobación de tipos
  • Matriz de control

Errores Semanticos 

Hemos mencionado algunos de los errores que la semántica analizador semántico se espera para reconocer:
  • No coinciden los tipos
  • Variable no declarada
  • Identificador reservado uso indebido.
  • Declaración de variables múltiples en un ámbito.
  • Acceder a una variable fuera de alcance.
  • Parámetro formal y real no coincide.

Gramatica Atributo

Atributo gramática es una forma especial de libres de contexto gramática donde algunas de las informaciones adicionales (atributos) se añade a una o más de sus terminales con el fin de proporcionar información sensible al contexto. Cada atributo tiene bien definido el dominio de los valores, como integer, float, caracteres, cadenas y expresiones.
Atributo gramática es un medio para proporcionar semántica en el contexto de libre gramática y puede ayudar a especificar la sintaxis y la semántica de un lenguaje de programación. Atributo gramática (cuando se considera como un árbol de análisis) puede pasar valores o información entre los nodos de un árbol.
Ejemplo:
E  E + T { E.value = E.value + T.value }
La parte derecha de la CFG contiene la semántica las normas que especifican el modo en que la gramática debe ser interpretado. Aquí, los valores de las terminales E y T se suman y el resultado se copian en el no-terminal E.
Semántica atributos pueden ser asignados a sus valores de su dominio en el momento de análisis y evaluación en el momento de la cesión o condiciones. Sobre la base de la forma los atributos obtienen sus valores, que pueden dividirse en dos categorías: atributos sintetizados y atributos heredados.

Atributos sintetizados

Estos atributos obtener los valores de los atributos de sus nodos secundarios. Para ilustrar, asumir las siguientes producciones:
S  ABC
Si S es tomar los valores de sus nodos secundarios (A,B,C), entonces se dice que es un atributo sintetizado, como los valores de ABC se sintetizan para S.
Como en nuestro ejemplo anterior (E → E + T), el nodo padre E obtiene su valor de su nodo hijo. Sintetiza los atributos nunca tomar valores entre sus nodos padres o cualquier nodos relacionados.

Atributos heredados

A diferencia de los atributos sintetizados, atributos heredados puede tomar valores entre padres y/o hermanos. Tal como se muestra en la siguiente producción,
S  ABC
Puede obtener los valores de S, B y C. B puede tomar valores de S, A, y C. Asimismo, C puede tomar valores de S, A y B.
Expansión: se produce cuando un no-terminal se ha ampliado a los terminales, como por una regla gramatical
Atributos Heredados
Reducción: Cuando un terminal se reduce a su correspondiente no-terminal según las reglas de gramática. Los árboles se analizan Sintaxis de arriba a abajo y de izquierda a derecha. Reducción cada vez que se produce, sus reglas semánticas correspondientes (acciones).
Análisis semántico utiliza la sintaxis dirige las traducciones para realizar las tareas antes mencionadas.
Analizador semántico recibe AST (Abstract Syntax Tree) de su etapa anterior (sintaxis).
Analizador semántico concede información sobre los atributos de AST, que son llamados atribuido AST.
Los atributos son dos tupla valor, <nombre de atributo, valor de atributo>
Por ejemplo:
int value  = 5;
<type, integer”>
<presentvalue, 5”>
Para cada producción, damos una regla semántica.

S-atribuyó SDT

Si un TRATO ESPECIAL Y DIFERENCIADO sólo utiliza atributos sintetizados, se llama como S-atribuidas al SDT. Estos atributos son evaluados usando S-atribuidas SDTS que tienen sus acciones semánticas escrito después de la producción (a la derecha).
S-atribuido SDT
Tal como se ha descrito anteriormente, los atributos de S-atribuidas SDTs son evaluados en la parte inferior de análisis, como los valores de los nodos padres dependen de los valores de los nodos secundarios.

L-atribuyó SDT

Esta forma de SDT de ambos atributos sintetizados y heredados con la restricción de no tomar los valores de derecha hermanos.
En L-atribuidas SDTs, un no-terminal pueden obtener los valores de su padre, hijo, y nodos relacionados. Tal como se muestra en la siguiente producción
S  ABC
S puede tomar valores de A, B y C (sintetizada). UNA puede tomar valores de S. B puede tomar valores entre S y A. C se puede obtener los valores de S, A y B. No Hay terminal puede obtener los valores de los hermanos a su derecha.
Los atributos de L-atribuidas SDTs son evaluados por primero en profundidad y de izquierda a derecha el análisis.
L-atribuido SDT

COMPILADORES: Análisis Sintáctico

Un compilador toma como su entrada un programa escrito en lenguaje fuente y produce un programa equivalente escrito lenguaje objeto. Un compilador se compone internamente de varias etapas o fases que ser realizan operaciones lógicas

 Análisis Sintáctico: Obtiene la cadena de tokens del analizador léxico y verifica que puede ser generada por la gramática que describe el lenguaje fuente



El análisis sintáctico es un análisis a nivel de sentencias, y es mucho más complejo que el análisis léxico. Su función es tomar el programa fuente en forma de tokens, que recibe del analizador léxico, y determinar la estructura de las sentencias del programa. Este proceso es similar a determinar la estructura de una frase en Castellano, determinando quien es el sujeto, predicado, el verbo y los complementos.

El análisis sintáctico agrupa a los tokens en clases sintácticas (denominadas no terminales en la definición de la gramática), tales como expresiones, procedimientos, etc.
El analizador sintáctico o parser obtiene un árbol sintáctico (u otra estructura equivalente) en la cual las hojas son los tokens, y cualquier nodo que no sea una hoja, representa un tipo de clase sintáctica (operaciones). Por ejemplo el análisis sintáctico de la siguiente expresión:
(A+B)*(C+D)
Con las reglas de la gramática que se presenta a continuación dará lugar al árbol sintáctico de la figura 3:
<expresión> ::= <término> <más términos>
<más términos>::= +<término> <más términos>| - <término> <más términos> | <vacío>
<término> ::= <factor> <más factores>
<más factores>::= * <factor> <más factores>|/ <factor> <más factores> | <vacío>
<factor> ::= ( <expresión> ) | <variable> | <constante>
La estructura de la gramática anterior refleja la prioridad de los operadores, así los operadores “+” y “-” tienen la prioridad más baja, mientras que “*” y “/” tienen una prioridad superior. Se evaluaran en primer lugar las constantes, variables y expresiones entre paréntesis.
Los árboles sintácticos se construyen con un conjunto de reglas conocidas como gramática, y que definen con total precisión el lenguaje fuente.
Al proceso de reconocer la estructura del lenguaje fuente se conoce con el nombre de análisis sintáctico (parsing). Hay distintas clases de analizadores o reconocedores sintácticos,
pero en general se clasifican en 2 grandes grupos: A.S. Ascendentes y A.S. Descendentes.
La principal tarea del analizador sintáctico no es comprobar que la sintaxis del programa fuente sea correcta, sino construir una representación interna de ese programa y en el caso en que sea un programa incorrecto, dar un mensaje de error.


Para ello, el analizador sintáctico (A.S.) comprueba que el orden en que el analizador léxico le va entregando los tokens es válido. Si esto es así significará que la sucesión de  símbolos que representan dichos tokens puede ser generada por la gramática correspondiente al lenguaje del código fuente.

Ejemplo: Entrada: “num*num+num Gramática: E ::= E + T | T T ::= T * F | F F ::= num (E: expresión, T: término, F: factor)


lunes, 20 de febrero de 2017

COMPILADORES: Análisis Léxico

Un compilador toma como su entrada un programa escrito en lenguaje fuente y produce un programa equivalente escrito lenguaje objeto. Un compilador se compone internamente de varias etapas o fases que ser realizan operaciones lógicas:

·         Analizador léxico: Lee la secuencia de caracteres de izquierda a derecha del programa fuente  y agrupa las secuencias de caracteres en unidades con significado propio(componente léxico o tokens). Las palabras clave, identificadores operadores constantes numéricas, signos de puntuación, como separadores de sentencias, llaves, paréntesis, etc. Son diversas clasificaciones de componentes léxicos.
l  
Lee los caracteres del programa fuente de izquierda a derecha, y los agrupa en tokens


👉 Funciones del Analizador Léxico 

  • Convierte el programa fuente en una cadena de tokens 
  • Para reconocer el token usa un patrón, una regla que describe como se forman las cadenas que corresponden a un token. 
  • Salta comentarios y espacios en blanco (tabuladores, saltos de línea...) 
  • Tener el registro de la línea del archivo fuente que está siendo analizada 
  • Genera mensajes de error léxico, y se recupera del error 
  • Convierte los valores literales al tipo que corresponda 
  •  Si la entrada debe obedecer a un formato, verifica el formato  
👉 Tokens y Lexemas 


Token
Lexema
q Elemento básico del lenguaje q Unidad léxica indivisible
q Identifica una entidad lógica dentro del lenguaje
q Incluyen: Palabras Reservadas, Constantes, Operadores, Signos de Puntuación e Identificadores
q La cadena original que se identifica como token  
q No hay correspondencia 1-1 entre token-lexema
q La cadena original que se identifica como token
q No hay correspondencia 1-1 entre token-lexema


EJEMPLO:



👉 Palabras Reservadas
  • Identificador es una palabra que inicia con una letra, y es seguida por letras o dígitos 
  • Las palabras clave cumplen con este mismo patron de construcción 
  • Se hace necesario un mecanismo que permita decidir cuando una cadena es una palabra clave o un identificador 
  • Solución sencilla: Palabras Reservadas (que no pueden ser usadas como identificadores)

👉 Manejo de Buffers 

  • Cuando se implementa el scanner es necesario manejar un buffer de entrada para hacer mas eficiente la lectura de la cadena de entrada 
  • Generalmente se define un buffer del tamaño de un bloque de disco
  •  Se maneja un apuntador que marca el inicio del lexema que se está analizando, y un apuntador que marca el carácter que está siendo analizado 
 Posibilidades: 

 Un buffer


 Par de Buffers



Sentinelas: marcar el final del buffer con EOF. Entonces EOF significa: 
  • Llegó al final del 1er. buffer : debe cargar el segundo 
  • Llegó al final del 2o. Buffer: debe cargar el primero
  • Llegó al final del archivo