Como compilar en lenguaje Java: sentencias extend e import

Visitas: 70  
Tiempo total: 0 días con 23:22:55 hrs  

Esta publicación está totalmente orientada hacia el desarrollo de un compilador en Java que compile Java, requiere que el lector tenga conocimientos de compilación (Del libro Compiladores: técnicas, principios y herramientas) y de programación en Java.

La sentencia extend en Java significa poder utilizar los métodos y atributos públicos de otra clase, así por ejemplo tengo la clase puerta que una de sus funcionalidades son abrir puerta, cerrar puerta, aplicar candado, cancelar candado. Ahora por ejemplo tenemos una clase carro, que tiene sus funciones y métodos totalmente separadas de la clase puerta, nos permitirá dividir el código y tener un mejor ordenamiento en nuestras clases al realizar un simple extend de la clase carro hacia la clase puerta.

La sentencia import sirve para utilizar las clases definidas en otros documentos Java, simplemente se incluye antes de la definición de la clase que se requiere compilar, o bien de la clase que contiene el método main.

Como implementar las funciones extend e import?

En realidad es bastante fácil porque esta solución es simple: analizar dichas instrucciones dinámicamente en el árbol de análisis sintáctico. El primer paso es construir la gramática que reconozca el lenguaje Java junto a las instrucciones extend e import, en realidad la primera sentencia que se debe de analizar es import – Por qué? Porque la clase principal Main después de realizar todos los extend (Puede que todas las clases estén en el archivo cargado, o puede que esté en otra que debe de ser importada) al importar otro documento, puede o no que contenga una sentencia extend que no será analizada por que las funciones mostradas aquí analizan o buscan las sentencias en el árbol actual que es generado dinámicamente – una sola vez.

E::= IMPORTACION
| public class id INICIO_CLASE
| protected class id INICIO_CLASE
| private class id INICIO_CLASE
| class id INICIO_CLASE
| error;

INICIO_CLASE::= llaves_inicio CUERPO_CLASE
| extends id llaves_inicio CUERPO_CLASE
| extends llaves_inicio CUERPO_CLASE
| CUERPO_CLASE
| extends id CUERPO_CLASE;

El ejemplo anterior es de CUP y permite en la gramática algunos errores comunes del usuario, omito el código que sirve para generar el árbol que analizaremos en las siguientes capas de desarrollo para proporcionar una mejor idea de la solución, la cual consiste en almacenar en el nodo la información que el usuario indica, por ejemplo para importación se almacenara únicamente el ID del archivo, sin su extensión .java. Para una extensión se hace lo mismo, se almacena el ID pero en este caso no es un archivo .java si no que es una clase que tiene asignado dicho ID.

Ahora que tenemos almacenado en sus respectivos nodos las sentencias import y extends <<Debe de ser la clase Main la que tenemos almacenada en el árbol que estamos analizando>> el análisis semántico debe de iniciar:

public void run(){
analizar_importaciones();
analizar_extends();
...
}

El inicio de este análisis debe de estar en la misma clase que implementa el árbol que se está analizando, es decir en la clase que tiene asignado en una variable el árbol de análisis sintáctico. Este método lo que hace es en el caso de las importaciones: Reemplazar el nodo de información de la sentencia import con el árbol sintáctico de dicho documento, en el caso de extend: Reemplazar el nodo que almacena la sentencia extend con los métodos y atributos públicos del sub-árbol (Por que la clase esta almacenada en un árbol que representa a un archivo .java cualquiera) de la clase que se indica con el ID.

En este ejemplo tenemos un árbol binario <<Mi implementación fue de cuatro nodos, para almacenar la información necesaria de cada sentencia y en el último nodo [Nodo 2, 3 o 4 de acuerdo a la instrucción] la siguiente instrucción>>, así que dinámicamente se buscara de izquierda a derecha cada sentencia extend e import:

public void analizar_importaciones(){
left=recorrer_por_importaciones(left);
right=recorrer_por_importaciones(right);
}

public void analizar_extends(){
left=recorrer_por_extends(left);
right=recorrer_por_extends(right);
}

A continuación la función que escanea en el árbol la sentencia import:

public arbol_java recorrer_por_importaciones(arbol_java hijo){
if(hijo!=null){
if(!hijo.nodo.equals("error")){
if(hijo.nodo.equals("import")){
File ext=new File(hijo.val+".java");
if(ext.exists()){
String texto=Proyecto2.load.leer_archivo(ext);
scanner_java scan1=new scanner_java(new BufferedReader( new StringReader(texto)));
parser_java parser=new parser_java(scan1);
try{
parser.parse();
}catch(Exception e){
Proyecto2.log.warn("Parser sintactico en arbol_java.java:"+e.toString());
}
Proyecto2.window.mostrar_errores();
//--
arbol_java importado=parser.getArbol();
//--
importado.left=importado.recorrer_por_importaciones(importado.left);
importado.right=importado.recorrer_por_importaciones(importado.right);
//
arbol_java nuevo_padre=new arbol_java();
nuevo_padre.nodo="nodo";
nuevo_padre.left=importado;
nuevo_padre.right=hijo;
nuevo_padre.right.nodo="nodo";
hijo=nuevo_padre;
}else{
Proyecto2.error.add(new errores("Semantico", 0, 0, "Archivo java importado "+hijo.val+" No existe"));
Proyecto2.window.update_console();
Proyecto2.window.mostrar_errores();
}
}else if(!hijo.nodo.equals("publicclass") && !hijo.nodo.equals("protectedclass") && !hijo.nodo.equals("privateclass") && !hijo.nodo.equals("class")){
hijo.left=recorrer_por_importaciones(hijo.left);
hijo.right=recorrer_por_importaciones(hijo.right);
}
}
}
return hijo;
}

Se puede observar cómo se buscan los nodos que están marcados como sentencias import, una vez se encuentran dichos nodos se lee el archivo .java denotado por el ID indicado por el usuario, en mi lenguaje no utilice puntos para describir las carpetas -Todos los archivos deben de estar en el mismo directorio-, a continuación con JFlex y con Cup generamos el árbol de análisis sintáctico de dicho fichero Java, en dicho nodo generado buscamos de nuevo sentencias import y reemplazamos el nodo actual por el nuevo nodo generado. Por último podemos observar que si la bandera del nodo actual es igual al inicio de la clase, omitimos su búsqueda por que de acuerdo a nuestro lenguaje no pueden existir sentencias import dentro del código de una clase.

La función para buscar sentencias extend debe de realizarse sobre el árbol raíz que contiene la clase Main, una vez se han importado todos los archivos se tendrán todas las clases que conforman el programa. En mi lenguaje se estableció la restricción de buscar únicamente clases que están protegidas (protected class) o que son públicas (public class). Una vez se copia en un árbol temporal la clase extendida (Publica o protegida) se buscan aquellos métodos y atributos que no son públicos, es decir que se empiezan a eliminar del árbol temporal los métodos que no utilizaremos.

Finalizando

El entorno de esta publicación es Netbeans, JFlex y Cup. El lenguaje a compilar fue Java (Con algunas limitaciones) y el resultado final fue p-seudo ensamblador que necesitaba correr en una consola Java. El método usado aquí por lo tanto está limitado a correr en una maquina virtual java –Bueno, no limitado- por que la generación del código tres direcciones y código objeto tendrán las libertades de dicha maquina, no así el código objeto que utiliza p-seudo ensamblador a partir del código 3 direcciones.


Para recibir boletines de información, por favor escribe tu correo electrónico:

Por favor ingrese un correo electrónico valido.
Registrado correctamente!