Autómatas y Lenguajes Formales - Edgar Alberto Quiroga Rojas.pdf
Lenguajes y Autómatas Unidad 1
-
Upload
rene-hernandez -
Category
Documents
-
view
52 -
download
0
description
Transcript of Lenguajes y Autómatas Unidad 1
2015
Alumno: Christian René Guerrero Hernández.N° de control: 12380909. Tutor: Ing. Fidel Ángel Martínez SalazarUnidad Académica: San Fernando.
RESUMEN UNIDAD 1
ASESOR:
Ing. Miguel Ángel Macías García
LENGUAJES Y AUTOMATAS 2
Instituto Tecnológico de Ciudad Victoria
Educación a Distancia
ANÁLISIS SEMÁNTICO.
Se compone de un conjunto de rutinas independientes, llamadas por los analizadores
morfológico y sintáctico.El análisis semántico utiliza como entrada el árbol sintáctico
detectado por el análisis sintáctico para comprobar restricciones de tipo y otras
limitaciones semánticas y preparar la generación de código.En compiladores de un solo
paso, las llamadas a las rutinas semánticas se realizan directamente desde el analizador
sintáctico y son dichas rutinas las que llaman al generador de código. El instrumento
más utilizado para conseguirlo es la gramática de atributos.En compiladores de dos o
más pasos, el análisis semántico se realiza independientemente de la generación de
código, pasándose información a través de un archivo intermedio, que normalmente
contiene información sobre el árbol sintáctico en forma lineal, para facilitar su manejo y
hacer posible su almacenamiento en memoria auxiliar.Las rutinas semánticas suelen
hacer uso de una pila (pila semántica) que contiene la información semántica asociada a
los operandos en forma de registros semánticos.
FUNCIONAMIENTO.
Recibe como entrada el árbol de derivación del programa añade al árbol de derivación
una serie de anotaciones, que permiten determinar la corrección semántica del
programa y preparar la generación de código La salida que genera es un árbol de
derivación con anotaciones semánticas.
FUNCIONES PRINCIPALES.
Identificar cada tipo de instrucción y sus componentes
Completar la Tabla de Símbolos
Realizar distintas comprobaciones y validaciones:
· Comprobaciones de tipos.
· Comprobaciones del flujo de control.
· Comprobaciones de unicidad.
· Comprobaciones de emparejamiento.
El Analizador Semántico finaliza la fase de Análisis del compilador y comienza la fase de
Síntesis, en la cual se comienza a generar el código objeto.
La especificación de la semántica puede realizarse de dos formas:
Lenguaje natural
Especificación formal: Semántica Operacional, semántica denotacional, semántica
Axiomática, Gramáticas con Atributos.
Este análisis es más difícil de formalizar, determina el tipo de los resultados intermedios,
comprobar que los argumentos que tienen un operador pertenecen al conjunto de
operadores posible, y si son compatibles entre sí.
ÁRBOLES DE EXPRESIÓN.
Los árboles de expresiones representan el código de nivel del lenguaje en forma de
datos. Los datos se almacenan en una estructura con forma de árbol. Cada nodo del
árbol de expresión representa una expresión, por ejemplo, una llamada al método o una
operación binaria, como x < y.
En la ilustración siguiente se muestra un ejemplo de una expresión y su representación
en forma de un árbol de expresión. Las diferentes partes de la expresión tienen un color
distinto para hacerlas coincidir con el nodo correspondiente del árbol de expresión.
También se muestran los diferentes tipos de los nodos del árbol de expresión.
REGLAS PARA LA CONSTRUCCION DE ARBOLES DE EXPRESION
Para construir el árbol de expresiones que represente la expresión matemática es
necesario construir primero la expresión pero en la notación polaca correspondiente y a
partir de esta es que se construye el árbol. El algoritmo usado para transformar una
expresión infija a prefija es explicado a continuación.
Sea A una expresión infija cualquiera, formada por operadores, paréntesis (izquierdos y
derechos) y operandos, también se usará una pila para los operadores. El procedimiento
seguido es el siguiente:
Se lee un elemento de A, si este es un operador o un paréntesis izquierdo, entonces se
actúa la regla 1 y si es un operando se envía la expresión de notación polaca. Si el
elemento leído de A es un paréntesis derecho, se desapilarán elementos de la pila de
operadores hasta encontrar el correspondiente paréntesis izquierdo. Cada elemento
desapilado pasa a formar parte de la notación polaca, excepto los paréntesis. Cuando no
queden elementos en A, se desipilan operadores de la pila, hasta que esta quede vacía.
Regla 1:
Existe un orden de prioridad para los operadores, que de menor a mayor es el siguiente:
suma (+) y resta (-), multiplicación (*) y división (/), exponenciación (^), operadores
unarios. El paréntesis izquierdo lo trataremos como un operador (aunque no lo es) cuyo
orden de prioridad es el mayor de todos cuando se quiera apilar y el menor de todos
cuando esté en la cima de la pila.
Cuando se intente apilar algún operador se hará lo siguiente: si es un operador unario
entonces se apila, si es un operador binario, se comparará su prioridad con el último
insertado en la pila (el de la cima), si su prioridad es mayor, entonces se apilará. Si
ocurre lo contrario (su prioridad es menor o igual) entonces el operador de la cima de la
pila se desapilará y pasará a formar parte de la notación polaca. Se volverá a intentar
apilar el operador siguiendo la misma regla, hasta que se pueda apilar, si la pila queda
vacía también se apila. El paréntesis izquierdo siempre se apilará y no podrá ser
desapilado por ningún operador y por tanto no formará parte de la notación polaca
inversa.
El siguiente ejemplo, ayudará a entender mejor lo dicho anteriormente. Sea la siguiente
expresión infija: 2^sin(y+x)–ln(x).
En la siguiente tabla se muestra paso a paso la conversión a notación postfija. Se usa el
color rojo para señalar los casos en que es necesario desapilar operadores de la pila.
CONSTRUCCIÓN DEL ÁRBOL BINARIO DE EXPRESIONES
Una vez obtenida la expresión en notación postfija, se puede evaluar mediante el uso
nuevamente de una pila. Sin embargo, en nuestro caso se trabaja con un árbol binario
de expresiones, así que lo que se hace es construir el árbol. El algoritmo usado para
construir el árbol no usa como tal la expresión postfija ya conformada, sino que el árbol
se va construyendo usando las mismas reglas con las que se construye la notación
postfija, una pila para los operadores y otra para los nodos del árbol, ambas no son
necesitadas al terminar el árbol. El algoritmo es el siguiente:
Se siguen las mismas reglas expuestas anteriormente usando la pila de operadores,
pero cuando se encuentra un operando o un operador es desapilado, entonces se crea
el nodo correspondiente y se actúa según la regla II. Al finalizar el algoritmo solo debe
quedar un nodo apilado en la pila de nodos, el que constituye el nodo raíz de nuestro
árbol de expresiones.
Regla 2.
Si el nodo corresponde a un operando, entonces se apila. Si el nodo corresponde a una
operador unario entonces se desapilan un nodo de la pila de nodos y es enlazado a la
rama izquierda del nodo correspondiente al operador unario y este último es apilado. Si
el nodo corresponde a un operador binario entonces dos nodos son desapilados de la
pila de nodos, el primero es enlazado a la rama derecha del nodo binario y el segundo a
la rama izquierda, nuevamente este nodo es apilado.
En el siguiente ejemplo se usa la misma expresión infija anterior (2^sen(y+x) – ln (x))
para ilustrar el procedimiento para construir el árbol:
Recorrido en preorden
En este tipo de recorrido se realiza cierta acción (quizás simplemente imprimir por
pantalla el valor de la clave de ese nodo) sobre el nodo actual y posteriormente se trata
el subárbol izquierdo y cuando se haya concluido, el subárbol derecho. Otra forma para
entender el recorrido con este método seria seguir el orden: nodo raíz, nodo izquierda,
nodo derecha.
Un recorrido general del árbol en orden previo Preorden produce la cadena
q+ABsenC*X+YZ. Es versión prefija. El recorrido general de orden produce una cadena
AB+CsenXYZ+*q, la cual es la versión posfija de la expresión.
Ejemplo de árbol con etiquetas: el nodo 2 tiene el operador + como etiqueta y sus hijos
izquierdo y derecho representan las expresiones a y b. por tanto, n2 representa (a)+(b) o
más simple, a+b. El nodo n1 representa (a+b)*(a+c) puesto que * es la etiqueta de n1, y
a+b y a+c son los correspondientes expresiones representadas por n2 y n3.
ACCIONES SEMÁNTICAS DE UN ANALIZADOR SINTÁCTICO.
El análisis semántico se realiza después del sintáctico y es más difícil de formalizarque
éste. Se trata de determinar el tipo de los resultados intermedios, comprobar quelos
argumentos que tiene un operador pertenecen al conjunto de los operadoresposibles, y
si son compatibles entre sí, comprobará que el significado de loque se va leyendo es
válido.El análisis semántico utiliza como entrada el árbol sintáctico detectado
paracomprobar restricciones de tipo y otras limitaciones semánticas y preparar
lageneración de código.
La salida teórica de la fase de análisis semántico sería un árbol semántico. Consiste en
un árbol sintáctico en el que cada una de sus ramas ha adquirido el significado quedebe
tener. En el caso de los operadores polimórficos el análisis semántico determina cuál es
el aplicable. Por ejemplo,consideremos la siguiente sentencia de asignación: A:= B + C
En Pascal, el signo “+” sirve para sumar enteros y reales, concatenar cadenas
decaracteres y unir conjuntos. El análisis semántico debe comprobar que B y C sean
deun tipo común o compatible y que se les pueda aplicar dicho operador. Si B y C
sonenteros o reales los sumará, si son cadenas las concatenará y si son
conjuntoscalculará su unión.
Acciones semánticas
Dependiendo del tipo de sentencias, las accionessemánticas pueden agruparse en:
• Sentencias de Declaración: completar la sección de tipos de la Tabla de Símbolos.
• Sentencias ejecutables: realizar comprobaciones detipos entre los operandos
implicados.
• Funciones y procedimientos: comprobar el número, orden y tipo de los parámetros
actuales en cada llamadaa una función o procedimiento.
• Identificación de variables: comprobar si identificador ha sido declarado antes de
utilizarlo.
• Etiquetas: comprobar si hay etiquetas repetidas y validación.
• Constantes: comprobar que no se utilicen en la parte izquierda de una asignación.
• Conversiones y equivalencias de tipo: verificación.
• Sobrecarga de operadores y funciones: detectar ysolventar.
COMPROBACIONES DE TIPOS EN EXPRESIONES.
La comprobación de tipos es una forma de asegurar quelos identificadores relacionados
sean de tiposcompatibles.
Dos identificadores son compatibles de acuerdo a lo siguiente:
• Cuando forman el lado izquierdo y el lado derecho de un operador.
• Cuando forman el lado izquierdo y el lado derecho de una proposición deasignación.
• Cuando sean parámetros reales y formales.
COMPROBACIÓN DE TIPOS EN EXPRESIONES
Las comprobaciones de consistencia que se efectúanantes de la ejecución del programa
fuente, sedenominan comprobaciones estáticas.Las comprobaciones que se realizan
durante laejecución del programa objeto se denominancomprobaciones dinámicas.
La revisión de la sintaxis de un programa fuente es un ejemplo de comprobación
estática, mientras que la comprobación de tipos, es un ejemplo de comprobación que
con frecuencia puede efectuarse en forma estática y que en ocasiones debe realizarse
dinámicamente.
TIPOS
¿Qué es un tipo?
La noción varía de lenguaje a lenguaje.
Consenso
· Un conjunto de valores
· Un conjunto de operadores sobre los valores
Las clases son una instanciación moderna de la noción de tipo
TIPOS Y OPERACIONES
Ciertas Operaciones son legales para cada tipo
No tiene sentido sumar un apuntador a función y un entero en C
Tiene sentido sumar dos enteros
Pero ambos tienen la misma implementación en lenguaje ensamblador!
PILA SEMÁNTICA EN UN ANALIZADOR SINTÁCTICO
Las pilas y colas son estructuras de datos que se utilizan generalmente para simplificar
ciertas operaciones de programación. Estas estructuras pueden implementarse
mediante arrays o listas enlazadas.
Pila: colección de datos a los cuales se les puede acceder mediante un extremo, que se
conoce generalmente como tope. Las pilas tienen dos operaciones básicas:
Push (para introducir un elemento)
Pop (para extraer un elemento)
Sus características fundamentales es que al extraer se obtiene siempre el último
elemento que acabe de insertarse. Por esta razón también se conoce como estructuras
de datos LIFO, una posible implementación mediante listas enlazadas seria insertando y
extrayendo siempre por el principio de la lista. Las pilas se utilizan en muchas
aplicaciones que utilizamos con frecuencia. Las pilas y colas son estructuras de datos
que se utilizan generalmente para simplificar ciertas operaciones de programación.
Estas estructuras pueden implementarse mediante arrays o listas enlazadas. Un
analizador sintáctico es un autómata de pila que reconoce la estructura de una cadena
de componentes léxicos. En general, el analizador sintáctico inicializa el compilador y
para cada símbolo de entrada llama al analizador morfológico y proporciona el siguiente
símbolo de entrada. Al decir pila semántica no se refiere a que hay varios tipos de pila,
hace referencia a que se debe programar única y exclusivamente en un solo lenguaje,
es decir, no podemos mezclar código de C++ con Visual Basic.
Ventajas
Los problemas de integración entre los subsistemas son sumamente costosos y
muchos de ellos no se solucionan hasta que la programación alcanza la fecha límite
para la integración total del sistema.
Se necesita una memoria auxiliar que nos permita guardar los datos para poder hacer
la comparación.
Reglas semánticas
Son el conjunto de normas y especificaciones que definen al lenguaje de programación y
están dadas por la sintaxis del lenguaje, las reglas semánticas asignan un significado
lógico a ciertas expresiones definidas en la sintaxis del lenguaje.La evaluación de las
reglas semánticas define los valores de los atributos en los nodos del árbol de análisis
sintáctico para la cadena de entrada. Una regla semántica también puede tener efectos
colaterales, por ejemplo, imprimir un valor o actualizar una variable global.
Compatibilidad de tipos
Durante la fase de análisis semántico, el compilador debe verificar que los tipos y
valores asociados a los objetos de un programa se utilizan de acuerdo con la
especificación del lenguaje. Además debe detectar conversiones implícitas de tipos para
efectuarlas o insertar el código apropiado para efectuarlas así como almacenar
información relativa a los tipos de los objetos y aplicar las reglas de verificación de tipos.
Analizadores descendentes:
Parten del axioma inicial de la gramática, se va descendiendo utilizando las derivaciones
izquierdas, hasta llegar a construir la cadena analizada. Se va construyendo el árbol
desde sus nodos terminales. Es decir, se construye desde los símbolos de cadena hasta
llegar al axioma de la gramática.
Bottom up
Es un principio de muchos años del estilo de programación que los elementos
funcionales de un programa no deben ser demasiado grandes. Si un cierto componente
de un programa crece más allá de la etapa donde está fácilmente comprensible, se
convierte en una masa de la complejidad que encubre errores tan fácilmente como una
ciudad grande encubre a fugitivos.
Top-down
Este método consiste en dividir los problemas en subproblemas más sencillos para
conseguir una solución más rápida. El diseño descendente es un método para resolver
el problema que posteriormente se traducirá a un lenguaje compresible por la
computadora. Un parser ascendente utiliza durante el análisis una pila. En esta va
guardando datos que le permiten ir haciendo las operaciones de reducción que necesita.
Para incorporar acciones semánticas como lo es construir el árbol sintáctico, es
necesario incorporar a la pila del parser otra columna que guarde los atributos de los
símbolos que se van analizando. Estos atributos estarían ligados a la correspondiente
producción en la tabla de parsing. La pila juega un papel fundamental en el desarrollo de
cualquier analizadorsemántico. Dentro de cada elemento de la pila se guardan los
valores que puedentener una expresión.
ESQUEMA DE TRADUCCIÓN
Un esquema de traducción es una gramática independiente de contexto en la que se
encuentran intercalados, en los lados derechos de las reglas de producción, fragmentos
de programa llamados acciones semánticas.Es como una definición dirigida por la
sintaxis con la diferencia de que el orden de evaluación de las reglas semánticas se
muestra explícitamente.Los esquemas de traducción pueden tener tanto atributos
sintetizados como heredados.
Traducción descendente
Se trabaja con esquema de traducción en lugar de hacerlo con definiciones dirigidas por
sintaxis, así que se puede ser explícito en cuanto al orden en que tienen que lugar las
acciones y las evaluaciones de los atributos.
Eliminación de la recursividad izquierda de un esquema de traducción
Como la mayoría de los operadores aritméticos son asociativos por la izquierda, es
natural utilizar gramáticas recursivas por la izquierda para las expresiones. La
transformación se aplica a esquemas de traducción con atributos sintetizados. Para el
análisis sintáctico descendente, se supone que una acción se ejecuta en el mismo
momento en que se expandiría un símbolo en la misma posición. Un atributo heredado
de un símbolo debe ser calculado por una acción que aparezca antes que el símbolo, y
un atributo sintetizado del no terminal de la izquierda se debe calcular después de que
hayan sido calculados todos los atributos de los que depende.
GENERACIÓN DE LA TABLA DE SÍMBOLO Y DE DIRECCIONES
Las tablas de símbolos (también llamadas tablas de identificadores y tablas de
nombres), realizan dos importantes funciones en el proceso de traducción: verificar que
la semántica sea correcta y ayudar en la generación apropiada de código. Ambas
funciones se realizan insertando o recuperando desde la tabla de símbolos los atributos
de las variables usadas en el programa fuente. Estos atributos, tales como: el nombre,
tipo, dirección de almacenamiento y dimensión de una variable, usualmente se
encuentran explícitamente en las declaraciones o más implícitamente a través del
contexto en que aparecen los nombres de variables en el programa.
Una de las estructuras de datos que se encuentran relacionadas con las fases del
proceso de compilación es la tabla de símbolos, la cual tiene como propósito registrar
información que se comparte entre varias etapas y que permite administrar los recursos
asociados a las entidades que manipulará el programa.
La tabla de símbolos tiene típicamente la siguiente estructura:
Una tabla de símbolos puede conceptualizarse como una serie de renglones, cada uno
de los cuales contiene una lista de valores de atributos que son asociados con una
variable en particular. Las clases de los atributos que aparecen en una tabla de símbolos
dependen en algún grado de la naturaleza del lenguaje de programación para el cual se
escribe el compilador.
Por ejemplo, un lenguaje puede ser sin tipos, y por lo tanto el atributo tipo no necesita
aparecer en la tabla. Similarmente, la organización de la tabla de símbolos variará
dependiendo de las limitaciones de memoria y tiempo de acceso.
MANEJO DE ERRORES SEMÁNTICOS
Los errores semánticos son pocos y los que existen no se pueden detectar tan fácilmente. Hasta esta etapa los errores son mostrados a los usuarios. Los demás errores ya son muy difíciles de detectar y generalmente se dan en tiempo de ejecución.
Manejo de errores semánticos
• Algunos problemas se presentan durante la fase de gestión de memoria al pasar argumentos o al crear la pila semántica.
• Muchos errores se generan durante la etapa del enlazador, al tratar de obtener código existente de algunas funciones/métodos ya implementadas en bibliotecas/APIs
El análisis semántico es posterior al sintáctico y mucho más difícil de formalizar que
éste. Se trata de determinar el tipo de los resultados intermedios, comprobar que los
argumentos que tiene un operador pertenecen al conjunto de los operadores posibles, y
si son compatibles entre sí, etc. En definitiva, comprobará que el significado de lo que se
va leyendo es válido. La salida teórica de la fase de análisis semántico sería un árbol
semántico. Consiste en un árbol sintáctico en el que cada una de sus ramas ha
adquirido el significado que debe tener. En el caso de los operadores polimórficos (un
único símbolo con varios significados), el análisis semántico determina cuál es el
aplicable.
REFERENCIAS
http://arantxa.ii.uam.es/~epulido/procesadores/semantico1.pdf
http://informatica.isipedia.com/primero/automatas-gramaticas-y-lenguajes/05-analisis-
semantico
http://biblioteca.uns.edu.pe/saladocentes/archivoz/publicacionez/
sesion_iii_3u___analisis_semantico.pdf
https://jesyib.wordpress.com/category/consultas/
https://sites.google.com/site/iscmendezportafolio/arboles-de-expresion
http://www.utm.mx/~jahdezp/archivos%20estructuras/arboles%20con
%20expresiones.pdf
https://es.scribd.com/doc/128330225/1-2-Acciones-semanticas
https://es.scribd.com/doc/219732692/Lenguajes-y-Automatas-II-Unidad-I-Sem-2-2013-
1#download
http://itpn.mx/recursosisc/7semestre/leguajesyautomatas2/Unidad%20I.pdf
http://dsc.itmorelia.edu.mx/~jcolivares/courses/ps207a/ps2_u5.pdf
http://revistas.udistrital.edu.co/ojs/index.php/visele/article/view/246/356