Você está na página 1de 26

Análisis Semántico

Clase 14
El Compilador hasta ahora
• Análisis léxico
– Detecta entradas de tokens legales

• Análisis Sintáctico
– Detecta entradas con árboles mal formados.

• Análisis Semántico
– Detecta todos los errores restantes
Por qué necesitamos el
análisis semántico
• El analizador sintáctico no puede detectar
todos los errores.
• Algunos constructores del lenguaje no son
libres de contexto
– Ejemplo: declaración de identificadores y su uso.
– No puedes utilizar una GLC para describir que
alguna palabra particulares aparezca dos veces
en una cadena separada por un texto en medio.
– Una versión abstracta del problema es:
{wcw | w  (a | b)* }

declaración uso
¿Qué hace el análisis
semántico?
• Verificaciones de varias clases, típicamente:
– Que todos los identificadores estén declarados.
– Los tipos de las expresiones y la compatibilidad
de las asignaciones.
– Invocación de métodos compatibles con las
declaraciones
– etc …
– Los requerimientos dependes del lenguaje.
Ámbitos
• Consiste en empatar las declaraciones de
identificadores con su uso
• El ámbito de un identificador es la porción del
programa en la cual el identificador es accesible.
• El mismo identificador se puede referir a diferentes
cosas en diferentes partes del programa.
– Ámbitos diferentes para el mismo nombre no se
sobreponen.
• Un identificador puede tener ámbitos restringidos.
– Es decir solo es visible en áreas particulares del
programa
Ámbitos estáticos vs
dinámicos
• La mayoría de los lenguajes tienen ámbitos
estáticos.
– El ámbito sólo depende del texto del programa no
de la conducta en tiempo de ejecución.
– Java, C, C++, Pascal, Modula, cool, etc. Tienen
ámbito estático.
• Pocos lenguajes tiene ámbitos dinámicos
– Lisp, SNOBOL
– El ámbito depende de la ejecución del programa
Ejemplo de ámbito estático
int x; -- variable global o campo estático
float convert_speed(float y)
{
float x; --una x diferente (variable local)
x=y*1.6
return x;
}
(El uso de x se refiere a la definición más
cercana )
Ámbito en lenguajes OO
• No todas las clases de identificadores siguen
la regla del anidamiento más cercano
• Por ejemplo las definiciones de clases
– Todas son visibles globalmente
• Declaración de campos en Java
– Visibles a todos los métodos en una clase
– Y algunas veces a otras clases también
• Dependiendo si son públicas o privadas, etc.
Más sobre ámbito
• Los nombres de métodos y atributos tienen
reglas complejas.
• Los nombres de campos son globales dentro
de cualquier clase.
• Pero los métodos y campos no necesitan estar
definidos en la clase en la cual se están
utilizando, pero si en una clase padre
(herencia)
• Los métodos se pueden redefinir
(sobreescritura)
Implementación de la regla
anidada más cercana
• La mayor parte del análisis semántico se
puede expresar como un recorrido
descendente recursivo de un árbol o AST.
– Procesa un nodo n
– Procesa los hijos de n
– Finaliza procesando el nodo n
• En cualquier porción del árbol (contexto en el
programa), necesitamos saber que
identificadores están definidos.
Tabla de Símbolos
• Una tabla de símbolos es una estructura de datos
empleada para registrar las declaraciones de
identificadores.
• Los identificadores se almacenan cuando se
declaran
– Con atributos
• Nombres de clases, métodos, variables, etc
– Y sub-atributos
• public, private, integer, float, static, array, etc
• Su localización en la pila si es variable local
– La tabla de símbolos se consulta para cualquier uso.
• Verificación semántica y generación de código.
Implementación de la tabla
de símbolos
• La estructura es una pila
– O una lista ligada que opera como pila
• Operaciones
– add_symbol(x) inserta x y la información asociada,
tal como el tipo, en la pila.
– find_symbol(x) busca en la pila, comenzando del
tope de la pila.Regresa el primer x encontrado o
NULL si no se encontro.
– remove_symbol() saca elemento de la pila
Una tabla de símbolos más
elaborada
• enter_scope() comenzar un nuevo ámbito
anidado
• find_symbol(x) encuentra el x actual (o null)
• add_symbol(x) agrega un símbolo x a la tabla
• check_scope(x) verdadero si x esta definido en el
ámbito actual (verificar declaraciones múltiples)
• exit_scope() salir del ámbito
– Descartar todos los símbolos del ámbito reciente
Definición de clase
• Los nombres de clases se pueden usar antes
de que sean definidos.
• Usualmente no se puede verificar esto para
los nombres de clases.
– Usando una tabla de símbolos (compilación
separada?)
– O en una pasada (a menos que se requieran
prototipos)
Definición de clase
• Solución usual
– Fase 1: Junta todos los nombres de clases (+
otras cosas)
– Fase 2: Realiza la verificación
• El análisis semántico completo requiere
varias pasadas.
– Probablemente más de una
• La mayoría de compiladores en JAVA busca
por archivos de clases previamente
compiladas.
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!
Sistema de tipos
• Un sistema de tipos de un lenguaje especifica las
operaciones que son válidas para cada tipo.
• La meta de la verificación de tipos es asegurar que
las operaciones se utilizan con los tipos correctos.
– Hace cumplir la interpretación de los valores.
– Algunas veces puede realizar conversiones automáticas
cuando el lenguaje permite modos mezclados y promoción
de tipos.
• El sistema de tipos provee una formalización concisa
de las reglas de verificación semántica.
Traslación dirigida por la
sintaxis
• La mayoría de los compiladores son de
múltiples pasadas.
• Recorre el AST para el análisis
semántico, verificación de tipos.
• Recorre este otra vez para
optimización.
• y generación de código
• .. etc.
Compilación de una pasada
• Bajo ciertas circunstancias, es posible construir
un compilador completo de una sola pasada.
• Es posible con ciertas condiciones del lenguaje
– Particularmente cuando se declara antes de que se
use.
• Esto requiere que la traslación se realice durante
un recorrido del árbol en profundidad.
– La forma en que el analizador sintáctico se mueve a
través del árbol
Verificación de tipos
• Existen dos aspectos a considerar en la verificación de tipos de
un compilador:
– Procesar las declaraciones y mantener una tabla de
símbolos.
• Almacenar el tipo de cada identificador en la tabla de
símbolos.
– Realizar la verificación de tipos y hacer cumplir las reglas
semánticas en expresiones y otros elementos del lenguaje
(e.g. Lista de argumentos, etc)
– Buscar el tipo de los identificadores usados.
– Inferir los tipos de constantes
– Calcular el tipo de los nodos que denotan expresiones.
Verificación de tipos con
aseveraciones
• En cada regla declaramos la aseveración que
se debe cumplir si el programa es válido.
• ASSERT(condición) ¨mensaje¨ significa:
– La condición se supone debe cumplirse
– Si no es verdadera, imprime mensaje.
• Puede usarse una macro en C para este
propósito
#define ASSERT(x,y) if (!x) printf(¨line %d: %s\n¨,lineno, (y))
Ejemplo de verificación de
tipos (un ejemplo similar esta
en la Sec. 6.4.4 de Kenneth)
Implementación de una
Tabla de Símbolos simple
Verificación de tipos en
Bison
Verificación de tipos (Cont.)

Você também pode gostar