Ejercicio de acciones semánticas de un analizador sintáctico
(5*2+3-9/3)-(5+2-3+9)
Examen
(6*7+5*5/3+7*7)+(4*7+3*8)=151.33
(6*9+4/5+8)-(9+6-7*7+5*6)=66.8
1.4 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. Objetivo teórico Es construir un árbol
de análisis sintáctico, este raramente se construye como tal, sino que las
rutinas semánticas integradas van generando el árbol de Sintaxis abstracta.
Se
especifica mediante una gramática libre de contexto. El análisis semántico
detecta la validez semántica de las sentencias aceptadas por el analizador
sintáctico. El analizador semántico suele trabajar simultáneamente al
analizador sintáctico y en estrecha cooperación. Se entiende por semántica como
el conjunto de reglas que especifican el significado de cualquier sentencia
sintácticamente correcta y escrita en un determinado lenguaje. Las rutinas
semánticas deben realizar la evaluación de los atributos de las gramáticas
siguiendo las reglas semánticas asociadas a cada producción de la gramática. El
análisis sintáctico es la fase en la que 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 la 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. 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. Las rutinas semánticas suelen
hacer uso de una pila que contiene la información semántica asociada a los
operadores en forma de registros semánticos. 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 analizador semántico. Dentro de cada
elemento de la pila se guardan los valores que pueden tener una expresión.
1.5
Esquema de traducción
Un esquema
de traducción es una gramática independiente de contexto en la que se asocian
atributos con los símbolos gramaticales y se insertan acciones semánticas
encerradas entre llaves { } dentro de los lados derechos de las producciones.
Los esquemas de traducción pueden tener tantos atributos sintetizados como
heredados. Cuando se diseña un esquema de traducción, se deben respetar algunas
limitaciones para asegurarse de que el valor de un atributo esté disponible
cuando una acción se refiera a él. Estas limitaciones, motivadas por las
definiciones con atributos por la izquierda, garantizan que las acciones no
hagan referencia a un atributo que aún no haya sido calculado. El ejemplo más
sencillo ocurre cuando sólo se necesitan atributos sintetizados, en este caso,
se puede construir el esquema de traducción creando una acción que conste de
una asignación para cada regla semántica y colocando esta acción al final del
lado derecho de la producción asociada. 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. Eliminacion 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. 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. Los fragmentos de código así insertados se denominan acciones
semánticas. Dichos fragmentos actúan, calculan y modifican los atributos
asociados con los nodos del árbol sintáctico. El orden en que se evalúan los
fragmentos es el de un recorrido primero-profundo del árbol de análisis
sintáctico. Obsérvese que, en general, para poder aplicar un esquema de
traducción hay que construir el árbol sintáctico y después aplicar las acciones
empotradas en las reglas en el orden de recorrido primero-profundo. Por
supuesto, si la gramática es ambigua una frase podría tener dos árboles y la
ejecución de las acciones para ellos podría dar lugar a diferentes resultados.
Si se quiere evitar la multiplicidad de resultados (interpretaciones
semánticas) es necesario precisar de qué árbol sintáctico concreto se está
hablando.
1.6
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.
1.7 Manejo de Errores Semánticos
Un error semántico se produce cuando la sintaxis del código es correcta, pero la semántica o significado no es el que se pretendía. La construcción obedece las reglas del lenguaje, y por ello el compilador o intérprete no detectan los errores semánticos. Los compiladores e intérpretes sólo se ocupan de la estructura del código que se escribe, y no de su significado. Un error semántico puede hacer que el programa termine de forma anormal, con o sin un mensaje de error. Hablando en términos coloquiales, puede hacer que el equipo se quede "colgado".
Un compilador es un sistema que en la mayoría de los casos tiene que manejar una entrada incorrecta. Sobre todo en las primeras etapas de la creación de un programa, es probable que el compilador se utilizará para efectuar las características que debería proporcionar un buen sistema de edición dirigido por la sintaxis, es decir, para determinar si las variables han sido declaradas antes de usarla, o si faltan corchetes o algo así. Por lo tanto, el manejo de errores es parte importante de un compilador y el escritor del compilador siempre debe tener esto presente durante su diseño. Hay que señalar que los posibles errores ya deben estar considerados al diseñar un lenguaje de programación. Por ejemplo, considerar si cada proposición del lenguaje de programación comienza con una palabra clave diferente (excepto la proposición de asignación, por supuesto). Sin embargo, es indispensable losiguiente:
1. El compilador debe ser capaz de detectar errores en la entrada;
2. El compilador debe recuperarse de los errores sin perder demasiadainformación;
3. Y sobre todo, el compilador debe producir un mensaje de error que permita al programador encontrar y corregir fácilmente los elementos(sintácticamente) incorrectos de su programa.
Comentarios
Publicar un comentario