sábado, 10 de octubre de 2009
Pseudocodigo
Compiladores
INTRODUCCIÓN
En 1946 se desarrolló el primer ordenador digital. En un principio, estas máquinas ejecutaban instrucciones consistentes en códigos numéricos que señalan a los circuitos de la máquina los estados correspondientes a cada operación. Esta expresión mediante códigos numéricos se llamó Lenguaje Máquina, interpretado por un secuenciador cableado o por un microprograma. Pero los códigos numéricos de las máquinas son engorrosos. Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves más fáciles de recordar que esos códigos numéricos; al final, todas esas claves juntas se traducían manualmente a Lenguaje Máquina. Estas claves constituyen los llamados lenguajes ensambladores, que se generalizaron en cuanto se dio el paso decisivo de hacer que las propias máquinas realizaran el proceso mecánico de la traducción. A este trabajo se le llama ensamblar el programa.
Clasificación
Los compiladores son programas de traducción insertados en la memoria por el sistema operativo para convertir programas de cómputo en pulsaciones electrónicas ejecutables (lenguaje de máquina). Los compiladores pueden ser de:
Compiladores incrementales: generan un código objeto instrucción por instrucción (en vez de hacerlo para todo el programa) cuando el usuario teclea cada orden individual.
El otro tipo de compiladores requiere que todos los enunciados o instrucciones se compilen conjuntamente.
Ensamblador: el lenguaje fuente es lenguaje ensamblador y posee una estructura sencilla.
Compilador cruzado: se genera código en lenguaje objeto para una máquina diferente de la que se está utilizando para compilar. Es perfectamente normal construir un compilador de Pascal que genere código para MS-DOS y que el compilador funcione en Linux y se haya escrito en C++.
Compilador con montador: compilador que compila distintos módulos de forma independiente y después es capaz de enlazarlos.
Autocompilador: compilador que está escrito en el mismo lenguaje que va a compilar. Evidentemente, no se puede ejecutar la primera vez. Sirve para hacer ampliaciones al lenguaje, mejorar el código generado, etc.
Metacompilador: es sinónimo de compilador de compiladores y se refiere a un programa que recibe como entrada las especificaciones del lenguaje para el que se desea obtener un compilador y genera como salida el compilador para ese lenguaje.
El desarrollo de los metacompiladores se encuentra con la dificultad de unir la generación de código con la parte de análisis. Lo que sí se han desarrollado son generadores de analizadores léxicos y sintácticos. Por ejemplo, los conocidos:
LEX: generador de analizadores léxicos.
YACC: generador de analizadores sintácticos desarrollados para UNIX. Los inconvenientes que tienen son que los analizadores que generan no son muy eficientes.
Descompilador: es un programa que acepta como entrada código máquina y lo traduce a un lenguaje de alto nivel, realizando el proceso inverso a la compilación.
Conceptualmente un compilador opera en fases. Cada una de las cuales transforma el programa fuente de una representación en otra.
Diagrama Earley
Es un algoritmo no determinístico de análisis sintáctico para las gramáticas libres de contexto descrito originalmente por Jay Earley. Se ordena, a los lados de los algoritmos CYK y GLR, entre los algoritmos que usan la noción de reparto (de cálculos y de estructuras) y que construyen todos los análisis posibles de una frase (y no sólo uno de estos análisis). Es uno de los algoritmos no determinísticos que usan ideas de la programación dinámica.
En caso de que la gramática fuese ambigüa (algo habitual en gramáticas del lenguaje natural), el tiempo de reconocimiento será proporcional al cubo de la longitud de la cadena de entrada, aunque para ciertas gramáticas el tiempo medio se puede reducir a cuadrático e incluso lineal.
El algoritmo de Earley se basa en tres operaciones o acciones que se realizan iterativamente a lo largo del análisis de la cadena de entrada:
ØAvanzar. Una vez realizadas todas las posibles predicciones de símbolos terminales, debemos comprobar que la cadena de entrada posee alguno de ellos y avanzar el puntero correspondiente del elemento {A --> ...a·..., i).
Earley en su forma básica
[Inicialización]
Para cada regla S --> ...
Nuevos = Añadir Elemento({S --> ·...,0}, Agenda[0])
Realizar
Nuevos = 0
Para cada elemento {A --> ...·B...,0} de Agenda[0]
Para cada regla B --> ...
Nuevos = Añadir Elemento({B --> ·...,0}, Agenda[0])
Para cada elemento {A --> ...·,0} de Agenda[0]
Para cada elemento {B --> ...·A...,0} de Agenda[0]
Nuevos = Añadir Elemento({B --> ...A·...,0}, Agenda[0])
mientras Nuevos = 1
[Iteración]
Para
Nuevos = 0
Para cada elemento {A --> ...·a...,i} de Agenda[j-1]
Si a pertenece a Entrada[j]
Nuevos = Añadir Elemento({A --> ...a·...,i}, Agenda[j])
Si Nuevos = 0
Devolver ERROR
Realizar
Nuevos = 0
Para cada elemento {A --> ...·B...,i} de Agenda[j]
Para cada regla B --> ...
Nuevos = Añadir Elemento({B --> ·...,j}, Agenda[j])
Para cada elemento {A --> ...·,i} de Agenda[j]
Para cada elemento {B --> ...·A...,k} de Agenda[i]
Nuevos = Añadir Elemento({B --> ...A·...,k}, Agenda[j])
mientras Nuevos = 1
Si existe algún elemento {S --> ...·,0} en Agenda[N]
Devolver BIEN
si no existe Ç
Devolver ERROR
Ejemplo
P → S # La regla de principio
S → S + M
| M
M → M * T
| T
T → número
La cadena vacía requiere un tratamiento especial, será un símbolo no terminal que no necesita de la operación completar para el avance de su puntero. Cada vez que añadamos un elemento a Agenda[i] del tipo {A --> ...· ..., j} donde el puntero · queda a la izquierda de una cadena vacía, añadiremos también el elemento {A --> ... · ..., j}. Al reconstruir la estructura de la cadena de entrada, en el bucle que recorre los símbolos de la parte derecha de una regla B --> B1 ... Bm debemos distinguir no sólo los terminales y no terminales sino también la cadena vacía, y al llegar al símbolo nulo del elemento {A --> ... ..., j} reduciremos sólo k y proseguiremos.
Algoritmo de reconstrucción del árbol sintáctico del algoritmo de Earley en ausencia de ambigüedades
Reconstruye({A --> B1 B2 ... Bm ·,i}, j)
Añade a Lista De Reglas el elemento (A --> B1 B2 B3 ... Bm)
k = m
l = j
Realizar
Si Bk es terminal
k = k-1
l = l-1
si no
Si existe en Agenda[l] un {Bk --> ·, r} tal que en
Agenda[r] esté {A --> B1 B2 ...·Bk, i}
Reconstruye({B --> ·, r}, l)
k = k-1
l = r
mientras k>0