Tema 5 - Resolución de Nombres en L0 con Referencias Adelantadas

Anuncio
TEMA 5: RESOLUCIÓN DE NOMBRES EN L-0 CON REFERENCIAS ADELANTADAS
Una referencia adelantada es un acceso simple o un identificador de tipo que en el momento que
lo encuentra el analizador aún no ha sido declarado.
EJEMPLO: El siguiente programa en L-0 presenta varias referencias adelantadas
modulo pila
clase Programa
{
inicio()
{
Pila p; // Referencia adelantada, la clase Pila aún no definida
entero i;
p := crear(Pila);
p.iniciar();
i:=1;
mientras (i<=10) hacer
p.apilar(i);
i:=i+1;
finmientras
}
}
inst clase Pila
{
oculto formación 100 entero almacen;
iniciar()
{
cima := 0; // Referencia adelantada al atributo cima
}
oculto entero cima;
apilar(entero elem)
{
si (no estaLlena()) entonces // Referencia adelantada al método estaLLena
cima := cima + 1;
almacen[cima] := elem;
finsi
}
oculto estaLlena() dev logico
{
si (cima = 100) entonces
dev cierto;
sino
dev falso;
finsi
}
oculto estaVacia() dev logico
{
si (cima = 0) entonces
dev cierto;
sino
dev falso;
finsi
}
desapilar() dev entero
{
si (no estaVacia()) entonces
cima := cima – 1;
dev almacen[cima+1];
sino
dev almacen[cima];
finsi
}
}
El problema que presenta la solución propuesta en el tema 4 es que pueden aparecer nombres
cuya declaración aún no está en la pila de ámbitos y que, por tanto, no podremos resolver.
Para solventar esto:
• Tenemos que almacenar todas las declaraciones, organizadas en ámbitos, para tenerlas
disponibles al final del análisis sintáctico. Para lo cual usaremos una tabla de ámbitos,
que no es más que una pila de ámbitos donde apilaremos todos los ámbitos que se
vayan completando durante el análisis.
• Los accesos simples que se puedan resolver durante el análisis los resolveremos,
según vimos en el tema 4, y el resto los almacena en una lista de accesos simples,
indicando el ámbito donde apareció. La estructura de los elementos de esta lista
contendrá:
o Un número que identifica un nombre de acceso simple y su ámbito contenedor.
Sólo habrá una entrada en la lista para todas las ocurrencias del mismo nombre
en el mismo ámbito.
o El nombre del acceso simple.
o El ámbito contenedor.
• La resolución de los identificadores de tipo se limitará a comprobar que se encuentran
declarados en el ámbito de MODULO. Al igual que los accesos simples, los que se
puedan resolver durante el análisis se resuelven, y el resto se coloca en una lista de
identificadores de tipo.
Los identificadores de tipo ya no se transforman en un ASA de declaración de clase, ya
que puede ocurrir que no se haya efectuado aún dicha declaración, por lo que se
almacenan como un nodo IDENT. Obviamente, esto complicará la tarea de asignar tipos
a las expresiones y requerirá que pasemos como parámetro a esta fase el ámbito de
modulo donde están declaradas todas las clases.
1.- ESTRUCTURAS DE DATOS
Las estructuras de datos que vamos a usar para implementar la pila de ámbitos son las
mismas que vimos en el tema 4: Pila, Ambito, Tabla_Simbolos, Simbolo y
Pila_Ambitos. En esta última incluimos un nuevo método buscar_simbolo_esp.
Pila_Ambitos.- extiende la clase Pila.
Métodos:
o Pila_Ambitos() – constructor de pila vacía.
o apilar_ambito(Ambito a) – apila un ámbito.
o desapilar_ambito() – desapila el ámbito de la cima de la pila.
o Ambito ambito_actual() – devuelve el ámbito actual (cima de la pila), si la
pila está vacía devuelve nulo.
o Simbolo buscar_simbolo(String s) – busca el símbolo s globalmente,
primero en el ámbito actual, si no lo encuentra en el contenedor, y así
hasta el ámbito global. Si no lo encuentra, devuelve nulo.
o Simbolo buscar_simbolo_esp(String s, Ambito a) – busca el símbolo s
globalmente, primero en el ámbito a, si no lo encuentra en el
contenedor, y así hasta el ámbito global. Si no lo encuentra, devuelve
nulo.
Además incluimos las siguientes estructuras:
Acceso.- Información de un acceso simple
Atributos:
o numero (int) – identificador único para cada nombre de acceso simple en
un ámbito.
o nombre (String) – nombre del acceso simple.
o contenedor (Ambito) – ámbito contenedor del acceso simple
Métodos:
o Acceso (int numero, Ambito contenedor) – constructor de acceso.
o String getNombre() – devuelve el nombre del acceso.
o String getNumero() – devuelve el numero identificador.
o Ambito getContenedor() – devuelve el ámbito contenedor del acceso.
o setNombre(String nombre) – establece el nombre.
o setNumero(int numero) – establece el numero.
o setContenedor(Ambito contenedor) – establece el ámbito contenedor.
Lista_Accesos.- Lista de accesos simples.
Atributos:
o Lista enlazada de Accesos.
o tamaño (int) – Número de elementos de la lista. Se inicializa a 0.
Métodos:
o Lista_Accesos() – constructor de lista vacía.
o Acceso getAcceso(int numero) – busca el acceso simple identificado por
numero en la lista de accesos. Si lo encuentra devuelve el acceso, y
nulo en caso contrario.
o String setAcceso (String nombre, Ambito contenedor) – busca el par
<nombre,contenedor>. Si lo encuentra devuelve el <numero> asociado.
Si no lo encuentra inserta un nuevo acceso de la forma
<numero,nombre,contenedor>
donde: <numero> = tamaño + 1;
tamaño = tamaño +1;
y devuelve el <numero> asociado.
Identificador_Tipo.Atributos:
o nombre (String) – nombre del identificador de tipo.
Métodos:
o Identificador_Tipo (String nombre) – constructor del identificador de tipo.
o String getNombre() – devuelve el nombre.
o setNombre(String nombre) – establece el nombre.
Lista_Identificadores.- Lista de identificadores de tipo.
Atributos:
o identificadores(Lista enlazada de identificadores de tipo).
Métodos:
o Lista_Identificadores() – constructor de lista vacía.
o ListaEnlazada getIdentificadores() – devuelve la lista de identificadores
de tipo.
o setIdentificador(Identificador_Tipo ident) – busca el <ident> en la lista de
identificadores. Si lo encuentra no hace nada. Si no encuentra lo añade
a la lista.
Tabla_Global.- Tabla con todos los elementos almacenados durante el análisis
Atributos:
o tablaambitos (Pila_Ambitos) – Tabla con todos los ámbitos y sus
declaraciones contenidas. Se inicializa con new Pila_Ambitos().
o ambitomodulo (Ambito) – ámbito de MODULO. Información redundante
(ya se encuentra en tablaambitos) pero facilita las búsquedas.
o accesossinresolver (Lista_Accesos) – Lista de accesos simples
pendientes de resolución. Se inicializa con new Lista_Accesos().
o identtiposinresolver (Lista_Identificadores) – Lista de identificadores de
tipo pendientes de resolución. Se inicializa con new
Lista_Identificadores()).
Métodos:
o Tabla_Global () – constructor de tabla vacía.
2.- FUNCIONES DE ÁMBITOS
Las funciones que nos facilitarán la tarea de resolución de nombres las agrupamos de la misma
forma:
(A) funciones que manipula la pila de ámbitos,
(B) funciones que permiten instalar declaraciones en los ámbitos,
(C) función que asocia significado a un acceso simple,
(D) función que asocia significado a un tipo clase.
A.- Manejo de la pila de ámbitos
Las siguientes funciones permiten crear y apilar ámbitos de distintos tipos (PROGRAMA,
MODULO, CLASE y MÉTODO), así como desapilar el ámbito actual. Incluimos una
función que permite instalar el ámbito actual de la pila de ámbitos en la tabla de ámbitos
antes de desapilarlo.
ambito_abrir_programa() – crea un ámbito global y lo coloca en la pila de ámbitos.
ambito_abrir( AST nombre, String tipo) – crea un ámbito con el nombre y tipo
indicados y lo coloca en la pila de ámbitos.
ambito_cerrar() – cierra, instala en la tabla de ambitos y desapila el ámbito actual.
1. Comprueba que la pila no está vacía
pilaambitos.vacia() == false;
2. Instala el ámbito actual a la tabla de ámbitos
ambito_instalar();
3. Desapila el ámbito actual
pilaambitos.desapilar_ambito();
ambito_cerrar_programa() – cierra, instala en la tabla de ambitos y desapila el ámbito
global.
1. Comprueba que la pila no está vacía
pilaambitos.vacia() == false;
2. Comprueba que el ámbito actual sea de tipo “PROGRAMA”
pilaambitos.ambito_actual().getTipo() == “PROGRAMA”;
3. Instala el ámbito actual a la tabla de ámbitos
ambito_instalar();
4. Desapila el ámbito actual
pilaambitos.desapilar_ambito();
ambito_instalar() – instala el ámbito actual de la pila de ámbitos en la tabla de ámbitos
y declaraciones. Si el ámbito actual es de tipo MODULO lo almacena de forma
redundante.
1. Añade el ámbito actual a la tabla de ámbitos
t.tablaambitos.apilar_ambito(pilaambitos.ambito_actual());
2. Si el ámbito actual es de tipo MODULO lo almacena
if( pilaambitos.ambito_actual().getTipo() == “MODULO”)
t.ambitomodulo = pilaambitos.ambito_actual();
B.- Instalación de declaraciones
Las siguientes funciones instalan declaraciones en la tabla de símbolos asociada al
ámbito actual. Adicionalmente, construye el ASA de cada tipo de declaración y lo
devuelve. Su implementación es igual que en el tema 4.
AST crear_declaracion_modulo (AST nombre_modulo, AST definicion_modulo) –
Construye un ASA para una declaración de módulo, instala dicha declaración en la tabla
de símbolos del ámbito actual y devuelve el ASA construido.
AST crear_declaracion_clase (AST nombre_clase, AST cualificador_clase, AST
definicion_clase) – Construye un ASA para una declaración de clase, instala dicha
declaración en la tabla de símbolos del ámbito actual y devuelve el ASA construido.
AST crear_declaracion_metodo (AST declaracion_metodo, AST
cualificador_metodo) – Construye un ASA para una declaración de método, instala
dicha declaración en la tabla de símbolos del ámbito actual y devuelve el ASA
construido.
AST crear_declaracion_atributo (AST nombre_atributo, AST tipo_atributo, AST
cualificador_atributo) – Construye un ASA para una declaración de atributo, instala
dicha declaración en la tabla de símbolos del ámbito actual y devuelve el ASA
construido.
AST crear_declaracion_parametro (AST nombre_parametro, AST tipo_parametro) –
Construye un ASA para una declaración de arámetro, instala dicha declaración en la
tabla de símbolos del ámbito actual y devuelve el ASA construido.
AST crear_declaracion_variable_local (AST nombre_variable, AST tipo_variable) –
Construye un ASA para una declaración de variable local, instala dicha declaración en la
tabla de símbolos del ámbito actual y devuelve el ASA construido.
C.- Tratamiento de accesos simples
Esta función resuelve el problema de asignar una declaración a un acceso simple. Si no
se puede asociar una declaración, o se trata de una referencia adelantada o de un error
de declaración.
AST ambito_tratar_acceso_simple (AST ident) – Busca el identificador en la pila de
ámbitos. Si lo encuentra construye un ASA para un acceso simple con el identificador y
la declaración asociada a él en la pila de ámbitos.
Si no lo encuentra, añade el acceso simple a la lista de accesos simples por resolver (si
no está ya) y construye un ASA para un acceso simple con el identificador y un nodo
LIT_ENTERO. En este nodo LIT_ENTERO se coloca como texto el número asociado a
dicho nombre en la lista de accesos por resolver.
ACCESO_SIMPLE
IDENT
LIT_ENTERO
LIT_ENTERO.Text
= nº del acceso
simple en la lista de
accesossinresolver
1. Obtiene el nombre del acceso simple
acc = ident.getText();
2. Busca el símbolo globalmente
if (s=pilaambitos.buscar_simbolo(acc) != null){ // ya se ha declarado
dec_acc = s.getDeclaracion();
ACC = #(#[ACCESO_SIMPLE,”acceso_simple”], ident, dec_acc);
}
else { // referencia adelantada (no se ha declarado aún) o error
no = t.accesossinresolver.setAcceso(acc, pilaambitos.ambito_actual());
LI = #(#[LIT_ENTERO,”lit_entero”]);
LI.setText(no);
ACC = #(#[ACCESO_SIMPLE,”acceso_simple”], ident, LI);
}
3. Devuelve el árbol ACC
D.- Tratamiento de identificadores de tipo
Esta función sólo comprueba que un identificador de tipo se corresponde con el nombre
de alguna clase.
AST ambito_tratar_ident_tipo (AST ident) – Busca el identificador en el ámbito de
“MODULO”. Si no lo encuentra, lo almacena en la lista de identificadores de tipo sin
resolver. En cualquier caso, devuelve el nodo IDENT que recibe como parámetro.
1. Obtiene el nombre del tipo clase
tipo = ident.getText();
2. Busca el símbolo en el ámbito de “MODULO”, que es donde están definidas
las clases
amb = pilaambitos.ambito_actual();
while (amb != Null && amb.getTipo() != “MODULO”)
amb = amb.getContenedor();
if (amb == Null)
error; // el tipo no es un tipo clase
simb = amb.getDeclaracion(tipo);
if (simb == null) { // referencia adelantada o error
// Añade el ident tipo a la lista de identtipo por resolver
IT = new Identificador_Tipo(tipo);
t.identtiposinresolver.setIdentificador(IT);
}
3. Devuelve ident
3.- ANÁLISIS SINTÁCTICO + ASA + RESOLUCIÓN DE NOMBRES
Las modificaciones que debemos hacer en el analizador del tema 4 son mínimas, ya que los
cambios se han efectuado en las estructuras y funciones. Se limita a añadir la declaración de
una variable de tipo Tabla_Global. Durante el análisis se rellenará dicha tabla y se devolverá al
programa principal, el cuál la pasará como parámetro a un treeparser para resolver los nombres
pendientes de resolución.
..........
..........
..........
{
// Declaración de la pila de ámbitos
Pila_Ambitos pilaambitos = new Pila_Ambitos();
// Declaración de la tabla de ámbitos y declaraciones global
Tabla_Global t = new Tabla_Global();
// Incluir todas las funciones de ámbitos descritas anteriormente
}
/////////////////////////////////////////////////////////
// DECLARACION DE MODULO
/////////////////////////////////////////////////////////
declaracion_modulo! returns [Tabla_Global tab]:
{ambito_abrir_programa();}
n:nombre_modulo {ambito_abrir (n,”MODULO”);}
d:definicion_modulo EOF
{ ambito_cerrar();
#declaracion_modulo=crear_declaracion_modulo(n,d);
ambito_cerrar_programa();
tab = t; }
;
..........
..........
..........
4.- RECORRIDO DEL ASA PARA RESOLVER NOMBRES
Lo siguiente que tiene que realizar el programa principal es un recorrido sobre el ASA generado por el
analizador sintáctico para resolver los nombres pendientes de resolver.
En cuanto a los accesos simples, hay que recordar que los que están pendientes de resolver se
encuentran en el ASA con la siguiente estructura: #(ACCESO_SIMPLE IDENT LIT_ENTERO). En el
recorrido se buscarán los accesos simples con dicha estructura y se sustituirá el nodo LIT_ENTERO por
el ASA de la declaración asociada del acceso simple (si la tiene), y que se buscará en la tabla de ámbitos
y declaraciones. Puesto que se va generar un nuevo ASA, con estos cambios, es necesario activar la
opción buildAST=true en las opciones del treeparser. El resto del ASA permanece igual.
Respecto a los identificadores de tipo, desde el comienzo disponemos de toda la información necesaria
para resolver los identificadores de tipo pendientes de resolver, ya que tenemos la lista de los mismos y el
ámbito de MODULO con las declaraciones de todas las clases definidas en el programa. Simplemente
habrá que recorrer dicha lista y comprobar que se encuentran entre las declaraciones contenidas en el
ámbito de MODULO.
Vamos a definir una función para resolver los accesos simples y otra para los identificadores de tipo.
void resolver_identificadores_tipo () – comprueba si todos los identificadores de tipo
pendientes de resolver se encuentra en el ámbito de MODULO. Si alguno no está
informa del error.
1. Obtiene la lista de identificadores de tipo sin resolver de la tabla de global
i = t.identtiposinresolver.getIdentificadores();
2. Recorre la lista y comprueba cada elemento (suponemos una lista enlazada)
ListIterator li = i.listiterator();
while (li.hasNext()){
elemento = (Identificador_Tipo) li.next();
nombre = elemento.getNombre();
if (t.ambitomodulo.getDeclaracion(nombre) == null)
error; // Identificador de tipo no declarado
}
AST resolver_acceso_simple (String numero) – Busca el ASA de declaración
asociado al acceso simple cuyo número en la lista de accesos sin resolver es el indicado
en el parámetro. El ASA de declaración lo busca en la tabla de ámbitos de la tabla
global, comenzando la búsqueda en el ámbito contenedor del acceso simple pendiente
de resolver.
1. Transformamos el numero (String) en un no (int)
no= Integer.parserInt(numero);
2. Obtiene el nombre y ámbito contenedor del acceso identificado por no
acc = t.accesossinresolver.getAcceso(no);
nom = acc..getNombre();
amb = acc.getContenedor();
3. Comprueba si ha sido declarado, buscando en la tabla de ámbitos y
declaraciones
if ( (sim = t.tablaambitos.buscar_simbolo_esp(nom, amb)) == null)
error; // acceso simple no declarado
else
dec = sim.getDeclaracio(); // referencia adelantada
4. Devuelve dec
El recorrido será el siguiente:
header{ ... }
class ResNom extends TreeParser;
options{
buildAST = true;
}
{
// Declaración de la tabla de ámbitos y declaraciones global
Tabla_Global t;
// Incluir las funciones para resolver identificadores y accesos
}
declaracion_modulo [Tabla_Global tab]:
{ t = tab;
resolver_identificadores_tipo(); }
#(MODULO nombre_modulo definicion_modulo);
nombre_modulo : IDENT ;
definicion_modulo: lista_declaraciones_clases
lista_declaraciones_clases: (declaracion_clase)*
;
declaracion_clase : #(CLASE nombre_clase cualificador_clase definicion_clase)
;
cualificador_clase : INST | NO_INST ;
nombre_clase : IDENT ;
definicion_clase : declaraciones_elementos_clase
;
declaraciones_elementos_clase : (declaracion_elemento_clase)* ;
declaracion_elemento_clase :
#(METODO declaracion_metodo cualificador_elemento_clase)
| #(ATRIBUTO IDENT tipo cualificador_elemento_clase)
;
cualificador_elemento_clase: OCULTO | VISIBLE ;
declaracion_metodo : prototipo_metodo definicion_metodo ;
prototipo_metodo : #(PROTOTIPO IDENT #(PARAMETROS declaracion_parametros)
#(RESULTADO (tipo | VACIO)))
;
declaracion_parametros : (declaracion_parametro)* ;
declaracion_parametro : #(PARAMETRO IDENT tipo) ;
definicion_metodo : #(DEFINICION #(VARIABLES_LOCALES declaracion_variables_locales)
#(INSTRUCCIONES instrucciones)
;
declaracion_variables_locales : (declaracion_variable_local)* ;
declaracion_variable_local : #(VARIABLE_LOCAL IDENT tipo) ;
instrucciones : (instrucción)* ;
instruccion : #(INSTRUCCION ( instruccion_simple | instruccion_compuesta)
;
instruccion_simple : llamada_metodo
| asignacion
| retorno
;
instruccion_compuesta :
condicion
| iteracion
;
asignacion : #(ASIGNACION expresion expresion ) ;
retorno : #(DEV expresion) ;
llamada_metodo : #(LLAMADA acceso #(EXPRESIONES lista_expresiones))
;
lista_expresiones : (expresion)* ;
condicion : #(SI expresion #(INSTRUCCIONES instrucciones) #(INSTRUCCIONES
instrucciones))
;
iteracion : #(MIENTRAS expresion #(INSTRUCCIONES instrucciones))
;
expresion: #(O expresion expresion)
| #(Y expresion expresion)
| #(NO expresion)
| #(MAYOR expresion expresion)
| #(MAYOR_IGUAL expresion expresion)
| #(MENOR expresion expresion)
| #(MENOR_IGUAL expresion expresion)
| #(IGUAL expresión expresion)
| #(DISTINTO expresion expresion)
| #(MAS expresion expresion)
| #(MENOS expresion expresion)
| #(MENOSUNARIO expresion)
| #(POR expresion expresion)
| #(DIVISION expresion expresion)
| #(LLAMADA acceso #(EXPRESIONES lista_expresiones))
| #(ACCESO_TABLA acceso #(EXPRESIONES lista_expresiones_nv))
| acceso
| LIT_ENTERO
| LIT_REAL
| LIT_CAR
| CIERTO
| FALSO
| NULO
;
acceso: #(ACCESO_SIMPLE IDENT declaracion_acceso)
| #(ACCESO_OBJETO #(ACCESO_SIMPLE IDENT declaracion_acceso) IDENT)
;
declaracion_acceso: declaracion_modulo
| declaracion_clase
| declaracion_elemento_clase
| declaracion_parametro
| declaracion_variable_local
| i: LIT_ENTERO { #declaracion_acceso = resolver_acceso_simple (i.getText()); }
;
lista_expresiones_nv : (expresion)+ ;
tipo :
tipo_predefinido_simple
| tipo_predefinido_compuesto
| IDENT
;
tipo_predefinido_simple :
ENTERO
| REAL
| LOGICO
| CARACTER
;
tipo_predefinido_compuesto : formacion
;
formacion : #(FORMACION #(LlSTA_ENTEROS lista_enteros)
(tipo_predefinido_simple | IDENT));
lista_enteros : (LIT_ENTERO)+ ;
Descargar