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)+ ;