Documentos de Académico
Documentos de Profesional
Documentos de Cultura
LENGUAJE Y AUTOMATAS II
ACTIVIDADES DE LA UNIDAD I
PROFESOR:
BEDOLLA SOLANO SILVESTRE
INTEGRANTES DE EQUIPO:
SÁNCHEZ JIMÉNEZ ISAAC DANIEL..................17320983
7° SEMESTRE
FECHA: 02/OCTUBRE/2020
1
ÍNDICE
Introducción....................................................................................................................3
Desarrollo.......................................................................................................................5
Actividad 1. Elabora 3 ejercicios y detecta el error semántico............................................................5
Actividad 2. Diseña y selecciona información sobre la construcción de un analizador semántico..................................9
Actividad 3. Reconocer el manejo de tipos en las expresiones y el uso de operadores....................13
Actividad 4. Establecer las reglas para la conversión de tipos (casting) en expresiones...................15
Actividad 5. Agregar acciones semánticas a la estructura de la gramática........................................17
Actividad 6. Manipular la tabla de conversión de símbolos y de errores y direcciones.....................21
Actividad 7. Integrar equipos de trabajo para la construcción de un analizador semántico.24
Conclusiones................................................................................................................29
Bibliografía...................................................................................................................30
Introducción
¿Qué es un compilador?
Un compilador son programas de computadoras que traducen un lenaguaje a otro.
Un compilador toma como su entrada un programa escrito en su lenguaje fuente y
produce un programa equivalente escrito en su lenguaje objetivo. (Ilustración 1)
Error de semántica
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. (Ilustración 2)
5
Entonces me dirijo a ese apartado y cambio mi variable de tipo int a variable string
para que se compile bien, y vemos que a la hora de compilar ya no marcara el error.
(Ilustración 3)
Ejercicio 2
El programa a continuación este hecho en el lenguaje de programación JAVA, la
función de tal programa es realizar una conversión de grados °F (Fahrenheit) a °C
(Celsius).
6
Sin embargo, al momento de compilar el programa nos muestra un error semántico.
El error semántico que nos muestra se debe a que a la variable a la que hemos
llamado f le hemos asignado un tipo de dato float, y a la otra variable que hemos
llamado c le hemos asignado el tipo de dato int. Específicamente, el error se debe a
que hemos intentado asignar un flotador a una variable de tipo entero.
Para que podamos compilar el programa sin ningún problema sería asignar a las
variables tipos de datos igual, en este, en ambas variables les asignaremos el tipo de
dato entero y como se muestra a continuación no tendremos ningún problema.
Ejercicio 3
Analizador semántico
Este es el ejemplo del analizador semántico, aquí mostrara los tipos de errores
semánticos que se generan de una tabla de símbolos que contiene el nombre de las
variables que declaran o que le asignan un valor de acuerdo a su tipo de datos con la
que fue definido.
Esta es la interfaz del programa.
a) texto de archivo: que es donde vamos a poner el programa a analizar.
b) tabla de símbolos: donde saldrá el tipo de datos que es cada uno.
c) código convertido: es básicamente donde saldrá el resultado del programa,
pero convertido a Basic.
d) lista de errores sintácticos y semánticos: aquí se mostrará los errores que el
analizador haya encontrado.
e) Generar tabla análisis lexico: este botón generara la “tabla de símbolos”.
f) Limpiar: es para borrar los datos analizados.
Analizador semántico
El analizador 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.
Ejemplo:
Ejemplo:
Ejemplo:
Aquí se cumple con lo que es la comparación, donde indica que el “53” es mayor que
“23” y donde el “80” es mayor que “26” así que cumple con la regla.
Ejemplo:
Ejemplo:
Operadores
Cualquier tipo entero pequeño como char o short es convertido a int o unsigned
int.En este punto cualquier pareja de operandos será int (con o sin signo), long, long
long, double, float o long double.
Char n;
Int a, b, c, d;
Float r, s, t;
a= 10;
b = 100;
r=1000;
c=a+b; caso 9, ambas son int
s=r+a; caso 3, a se convierte a float
d=r+b; caso 3, b se convierte a float
d=n+a+r; caso 1, n se convierte a int, la operación resultante corresponde al caso 3, el
resultado (n+a) se convierte a float
t = r+a s +c; caso 3, a se convierte a float, caso 4 (r+a) y s son float, caso 4, c se
convierte a float.
(DeclVariables) → (ListaIds):(Tipo)
{para v en (ListaIds).l hacer: declara_var(v, hTipoi.t) fin para}
(ListaIds) → id,(ListaIds)1 {(ListaIds).l:= concat( [id.lexema],
(ListaIds)1.l)}
(ListaIds) → id {(ListaIds).l:= [id.lexema]}
Con lo que hemos visto, podemos decir que un esquema de traducción consta de:
Una gramática incontextual que le sirve de soporte.
Un conjunto de atributos asociados a los símbolos terminales y no terminales.
Un conjunto de acciones asociadas a las partes derechas de las reglas.
Dividimos los atributos en dos grupos:
Atributos heredados.
Atributos sintetizados.
Sea (A) → (X1)…(Xn) una producción de nuestra gramática. Exigiremos
qué: Las acciones de la regla calculen todos los atributos sintetizados de
(A).
Las acciones situadas a la izquierda de (Xi) calculen todos los atributos heredados de
(Xi).
Ninguna acción haga referencia a los atributos sintetizados de los no terminales
situados a su derecha en la producción.
Las dos últimas reglas nos permitirán integrar las acciones semánticas en los
analizadores descendentes recursivos. Cuando se emplean, se dice que el esquema
de traducción está basado en una gramática L-atribuida. La L indica que el análisis y
evaluación de los atributos se pueden hacer de izquierda a derecha.
La interpretación de las acciones como sentencias que se ejecutan al pasar el
análisis por ellas permite implementar los esquemas de traducción de manera
sencilla. Para ello se modifica la implementación del analizador recursivo
descendente correspondiente a la gramática original de la siguiente manera:
Los atributos heredados del no terminal (A) se interpretan como parámetros
de entrada de la función Analiza_A.
Los atributos sintetizados del no terminal (A) se interpretan como parámetros
de salida de la función Analiza_A.
Las acciones semánticas, una vez traducidas al lenguaje de programación
correspondiente, se insertan en la posición correspondiente según su orden
en la parte derecha donde aparecen.
En la práctica, es frecuente que, si el lenguaje de programación (como C) no permite
devolver más de un valor, los atributos sintetizados del no terminal se pasen por
referencia.
El análisis semántico se realiza después del sintáctico y es 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í, es decir, comprobará que el significado de lo
que se va leyendo es válido. El análisis semántico utiliza como entrada el árbol
sintáctico detectado para comprobar restricciones de tipo y otras limitaciones
semánticas y preparar la generació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 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.
Por ejemplo, consideremos la siguiente sentencia de asignación: A := B + C.
Ejemplo
En Pascal, el signo “+” sirve para sumar enteros y reales, concatenar cadenas de
caracteres y unir conjuntos. El análisis semántico debe comprobar que B y C sean de
un tipo común o compatible y que se les pueda aplicar dicho operador. Si B y C son
enteros o reales los sumará, si son cadenas las concatenará y si son conjuntos
calculará su unión.
Dependiendo del tipo de sentencias, las acciones semánticas pueden agruparse en:
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 linealizada (para facilitar su manejo y hacer posible su
almacenamiento en memoria auxiliar).
En cualquier caso, las rutinas semánticas suelen hacer uso de una pila (la pila
semántica) que contiene la información semántica asociada a los operandos (y a
veces a los operadores) en forma de registros semánticos.
Propagación de atributos
Sea la expresión
int a,b,c;
a/(b+c^2)
El árbol sintáctico es:
/
| |
a +
| |
b ^
| |
c 2
De la instrucción declarativa, la tabla de símbolos y el analizador morfológico
obtenemos los atributos de los operandos:
/
| |
a +
int
| |
b ^
int
| |
c 2
int int
Propagando los atributos obtenemos:
/ int
| |
a + int
int
| |
b ^ int
int
| |
c 2
int int
Si la expresión hubiera sido
a/(b+c^-2)
El árbol sintáctico sería el mismo, sustituyendo 2 por -2. Sin embargo, la propagación
de atributos sería diferente:
/ real
| |
a + real
int ---------
| |
b ^ real
int ---------
| |
c -2
int int
En algún caso podría llegar a producirse error (p.e. si / representara sólo la división
entera).
Si la expresión hubiera sido
int a,b,c,d;
a/(b+c^d)
El árbol sintáctico sería el mismo, sustituyendo 2 por d. Sin embargo, la propagación
de atributos sería incompleta:
/ {int,real}
| |
a + {int,real}
int
| |
b ^ {int,real}
int
| |
c d
int int
El analizador semántico podría reducir los tipos inseguros al tipo máximo (real) o
utilizar un tipo interno nuevo (ej. arit={int,real}, una unión).
Lo anterior es un ejemplo de propagación bottom-up. La propagación top-down
también es posible: lo que se transmite son las restricciones y los tipos de las hojas
sirven de comprobación. Por ejemplo, si la división sólo puede ser entera,
transmitimos hacia abajo la restricción de que sus operandos sólo pueden ser
enteros. Al llegar a d, esa restricción se convierte en que d debe ser positiva. Si no lo
es, error.
La implantación de todos los casos posibles de operación con tipos mixtos podría ser
excesivamente cara. En su lugar, se parte de operaciones relativamente simples (ej.
int+int, real+real) y no se implementan las restantes (ej. int+real, real+int), añadiendo
en su lugar operaciones monádicas de cambio de tipo (ej. int->real).
Esta decisión puede introducir ambigüedades. Por ejemplo, sea el programa
real a;
int b,c;
a:=b+c
El árbol sintáctico es:
:=
| |
a +
real
| |
b c
int int
Existen dos conversiones posibles:
:= real := real
| | | |
a + real a + int
real --------- real ---------
| | ||
b c b c
int int int int
El problema es que no tenemos garantía de que los dos procedimientos sean
equivalentes. El segundo puede dar overflow, la primera pérdida de precisión. La
definición del lenguaje debe especificar estos casos.
Las transformaciones posibles se pueden representar mediante un grafo cuyos
nodos son los tipos de datos y cada arco indica una transformación. Dado un
operando de tipo A que se desea convertir al tipo B, se trata de encontrar una
cadena de arcos que pase de A a B en el grafo anterior. Podría haber varios grafos,
cada uno de los cuales se aplicará en diferentes condiciones, por ejemplo, uno para
las asignaciones, otro para las expresiones, etc.
Conclusiones
Es difícil el prever todos los posibles errores semánticos, por lo que es igual de
complicado el generar el código correspondiente.