FACULTAD DE INGENIERÍA E.A.P. E.A.P. INGENIERÍA DE SISTEMAS E INFORMÁTICA
MANUAL DEL CURSO:
Teoría de Lenguajes (Unidad III) Tema:
Descripción de lenguajes de programación
Dictado por: DIANA CECILIA MUÑOZ CASANOVA M.S. en Ingeniería de Sistemas e Informática
CHIMBOTE – PERÚ 2007
CAPÍTULO I: INTRODUCCIÓN A LOS COMPILADORES 1.1. 1.2. 1.3. 1.4. 1.5. 1.6.
Introducción Historia Conceptos básicos Concepto de compilador Int Interpret retar vs compilar Tipos de compiladores res
11001 104 108 109 110 112
CAPÍTULO II: CONCEPTOS FUNDAMENTALES DE LOS COMPILADORES 2.1 2.1.
2.2. 2.3. 2.3. 2.4. 2.4. 2.5 2.5. 2.6. 2.6.
Estr Estruuctu ctura de un com omppila ilador dor. 2.1.1.Preprocesador 2.1.2.Compilación 2.1.3.Ensamblado 2.1.4.Enlazado Proceso de compilación 2.2.1.Fase de análisis 2.2.2.Fase de síntesis Ejem Ejempl ploo del del proc proces esoo de comp compil ilac ació iónn Herra Herrami mien enta tass para para cons constr truc ucci ción ón de comp compil ilad ador ores es El lengu enguaaje y la herra rrami mieenta nta Aspec Aspectos tos acadé académi micos cos y de inv inves estig tigaci ación ón de compil compilado adores res
114 115 115 115 116 117 118 119 120 120 125 125 126 126 127
CAPÍTULO III: ANÁLISIS LÉXICO 3.1 3.1. 3.2 3.2. 3.3 3.3. 3.4. 3.4. 3.5. 3.5. 3.6. 3.6. 3.7. 3.7.
Anál Análiisis sis léxic éxicoo (sc (scanner nner)) El proc roceso eso del anál análiisis sis léxico xico ¿que ¿que es un anali nalizzador dor léx léxico ico? Func Fu ncio ione ness del del anal analiz izad ador or léxi léxico co Nece Necesi sida dadd del del anal analiz izad ador or léxi léxico co Ventaj entajas as de separ separar ar el análi análisis sis léxic léxicoo y el análi análisis sis sintá sintácti ctico: co: Comp Compon onen ente tess léxi léxico cos, s, patr patron ones es,, lexe lexema mass 3.7.1.Componente 3.7.1.Comp onente léxico o token 3.7.2.Patrón o expresión regular 3.7.3.Lexema 3.8. 3.8. Desc Descri ripc pció iónn de un anal analiz izad ador or léxi léxico co 3.9. Unidades de léxico 3.10 3.10.. El rol rol del del anal analiz izad ador or léxi léxico co 3.1 3.11. Trata ratami mien ento to de los los erro errore ress 3.12 3.12.. Trata ratami mien ento to de pala palabr bras as rese reserv rvad adas as 3.13. 3.13. Constr Construcc ucción ión de un anali analizad zador or léxico léxico 3.14 3.14.. Conc Concep epto to de expr expres esió iónn regu regula larr 3.15 3.15.. Defi Defini nici ción ón de expr expres esió iónn regu regula larr 3.16 3.16.. Oper Operac acio ione ness de expr expres esio ione ness regu regula lare ress
128 128 128 128 129 129 130 130 131 131 133 133 133 133 134 134 136 136 137 138 138 139 139 140 140 140 141 141 141 141 142 142
3.17. 3.17. 3.18 3.18.. 3.19 3.19.. 3.20 3.20.. 3.21 3.21.. 3.22 3.22..
Lengua Lenguaje je descri descrito to por una expres expresión ión regul regular ar Teore eorema mass de equi equiva vale lenc ncia ia Matr Matric ices es de tran transi sici ción ón Repr Repres esen enta taci ción ón de los los autó autóma mata tass Autó Autóma mata ta fini finito to dete determ rmin inis ista ta Autó Autóma mata ta fini finito to no dete determ rmin inis ista ta
142 143 143 143 143 144 144 145 145 147 147
CAPÍTULO IV: ANÁLISIS SINTÁCTICO 4.1. 4.2. 4.2. 4.3 4.3. 4.4.
Gramáticas Gram Gramát átic icas as li libre bress de cont contex exto to y anál anális isis is sint sintác ácti tico co Gram Gramát átiicas cas libre bres de conte ntexto xto Conceptos sobre GLCS 4.4.1.Árbol 4.4.1.Árb ol de derivación 4.4.2.Gramáticas 4.4.2.Gra máticas no ambiguas 4.4.3.Gramáticas 4.4.3.Gra máticas ambiguas 4.5. Gramática BNF 4.6. 4.6. Árbo Árbole less de anál anális isis is sint sintác ácti tico coss 4.7 4.7. Exte Extennsió sión de la not notaci ación BNF 4.8 4.8. La notac otaciión BNF exte xtendi ndida 4.9. 4.9. El proc proces esoo de anál anális isis is sint sintác ácti tico co 4.10 4.10.. Anál Anális isis is sint sintác ácti tico co asce ascend nden ente te 4.1 4.11. Anal Analiz izad ador or sint sintác ácti tico co SLR SLR 4.12 4.12.. Anál Anális isis is sint sintác ácti tico co desc descen ende dent ntee 4.13 4.13.. Anal Analiz izad ador or con con retr retroc oces esoo 4.14 4.14.. Técn Técnic icas as de anál anális isis is pred predic icti tivo vo 4.15 4.15.. Con Conjunt juntos os de pred predic icci ción ón 4.1 4.16. Con Conjunto unto prim rimero ero 4.17 4.17.. Con Conjunt juntoo sigu siguie ient ntee 4.18 4.18.. Fact Factor oriz izac ació iónn por por la izqu izquie ierd rdaa 4.19 4.19.. Elim Elimin inac ació iónn de la recu recurs rsiv ivid idad ad
149 151 151 151 151 152 152 153 154 156 157 157 158 158 158 158 158 158 159 159 161 161 163 163 164 164 165 165 165 165 165 165 166 166 166 166 166 166
CAPÍTULO I: INTRODUCCIÓN A LOS COMPILADORES 1.1. INTRODUC INTRODUCCIÓN CIÓN En un mundo informatizado como en el que vivimos, en el que cada día que pasa dependemos más y más de un sistema informático eficiente, el cual debe estar preparado para brindarnos la más alta calidad de servicios y prestaciones. Además Además de ser sencillo en su manejo y sobre todo confiable, siendo estas características indispensables para el usuario final. Quien no se fija, ni pregunta como se realiza determinada tarea, pero si es buen critico a la hora de ver resultados, pero hay otros que contrarios a estos, se hace la pregunta del millón, "¿Cómo se logra hacer tal y tal cosa? ,"¿Cómo es posible que un graficador trabaje tan rápido?, ¿Cómo es posible que un procesador de palabra a la hora de usar un diccionario sea tan eficiente?, ¿Cómo es posible llevar los resultados de una aplicación a otra?, o ¿Cómo es posible que un programa que fue creado por una empresa puede trabajar con los datos de obtenidos de otro programa, echo por otra empresa?. Muchas Muchas pueden pueden ser las respue respuesta stas, s, alguno algunoss argum argument entara arann que que es el sistem sistemaa operativo, otros dirán que son las normas y estándares establecidos, otros dirán irónicamente irónicamente que es más sencillo de lo que se piensa, dirán que se hace clac con la rata aquí, se arrastra y se lleva a donde se quiere. Todos ellos tienen razón, sin embargo si indagamos más a fondo. Surgirán preguntas más directas como por ejemplo "¿Cómo se logra tal velocidad, con tan buen manejo de gráfico?", claro que a todas ellas se puede responder diciendo, que todo se logra gracias al Hardware, y no estaríamos totalmente errados, porque un buen Hardware conlleva a un buen resultado, a una buena calidad de impresión en caso de volcado al papel, una buena imagen si hablamos de gráficos, o un buen tiempo de respuesta a la hora de realizar algún calculo matemático, pero siempre el Hardware será una parte, solo una parte. Es en este punto donde surge el Software, los programas, o como el modismo denota hoy en día, las aplicaciones. Es decir que para obtener un buen resultado no solo hace falta un buen Hardware acorde a los requerimientos de la tarea que se quiere realizar, sino que calidad, eficiencia, velocidad, y confiabilidad hoy en día son sinónimos de un buen trabajo en conjunto de la dupla Hardware y Software. Dejando de lado lo que es el Hardware, y profundizando lo que representa su par,
pal palab abra ra ya adop adopta tada da en nues nuestr troo idio idioma ma,, y mu muyy usad usadaa en el mu mund ndoo de la informática, deducimos que para obtener un buen software, ante todo esta el aspecto creador de quien lo realiza, luego hay que ver cual será el entorno de trabajo en que actuara, cuales serán los requerimientos del mismo, hay que saber elegir que paradigma de programación se usará. Después de formuladas estas preguntas y de haber respondido a las mismas de manera apropiada. Hay que elegir cual es el lenguaje de programación más conveniente para realizar dicha tarea. Un lenguaje de programación o más genéricamente de computación, es el medio por por el cual cual el homb hombre re inte intera ract ctúa úa con con un orde ordena nado dorr. Pero Pero el leng lengua uaje je de computación computación no es lo único que se necesita para que se produzca la comunicación, comunicación, hace falta otro componente importante para completar el medio de comunicación, y es en este punto donde surge la palabra "interprete", cuya definición dice "persona que explica a otra en lengua que entienda" en el ámbito informático, olvi olvidé démo mono noss por por un inst instan ante te de la pala palabr braa pers person ona, a, e "int "inter erpr pret etem emos os"" su significado. significado. Se puede decir entonces que a través de un intérprete podríamos pedir a un ordenador que realice una tarea determinada sin preocuparnos de los detalles. Claro que este interprete no es más que una aplicación (programa), que realiza la traducción de lo que pedimos que comunique a un ordenador, por lo tanto un interprete es capaz de conocer dos lenguajes el del emisor y el del receptor. En el mundo de la informática existen muchos lenguajes de programación, que trabajan con uno o varios paradigmas de programación (estilos, formas, métodos de programación), por lo tanto es de suponer que existen distintas reglas de sintaxis y semántica para cada lenguaje de programación como lo existe en cualquier otro lenguaje, sea cualquiera el tipo y contexto al que pertenezca. En tiempos ya muy remotos según cuentan los que saben proliferaban los intérpretes, quienes tomaban la petición del usuario (hombre) e "interpretaban" la misma y se la comunicaban al ordenador quien la ejecutaba, y esperaba una nueva petición. La petición era declarada dentro los parámetros de definición del lenguaje usado. Con el tiempo se vio que el estar realizando una interpretación y traducción cada vez que se necesita realizar algo era poco efectivo, en cuanto a tiempo de trabajo del Hard Hardwa ware re se refi refier ere, e, y más más aun aun si se trat tratab abaa de un conj conjun unto to gran grande de de instrucciones (peticiones), es aquí donde entraron en juego los compiladores, quien al igual que sus antecesores realizan una traducción de los programas
(conjunto de intrusiones de un lenguaje) revisando que este dentro del marco de definición del lenguaje de programación utilizado. Con la diferencia que la traducción se realiza una sola vez y de todo el programa. Después de todos estos tópicos previos, podemos decir que la calidad de un buen software es producto de un lenguaje de computación versátil, flexible y veloz, todo sinónimo de buen compilador, claro sin dejar de lado la capacidad creadora del programador (usuario, nosotros). "¿Cómo funciona un compilador? ", es la pregunta de todo aquel que se hace llamar programador, programador, a grandes rasgos un compilador toma un programa escrito en un lenguaje de programación con una gramática, sintaxis y semántica definida, revisa que este dentro de las pautas de definición del lenguaje, y lo traduce en programa de salida escrito en lenguaje binario, el cual es entendido por el ordenador y por lo tanto puede ser ejecutado por el mismo (recordar que un interprete a diferencia de un compilador no traduce todo el programa sino que va realizando la traducción traducción e interpretación de la misma paso a paso, sin crear ningún programa de salida ejecutable). Las partes del proceso de compilación se dividen en dos: una llamada fase de Análisis y otra llamada fase de Sintaxis, las cuales interactúan entre si para la creación de la tabla de símbolos y el control del maneja manejador dor de errore errores, s, dentr dentroo del análi análisis sis hay tres tres etapa etapass ll llama amadas das análi análisis sis lexicográfico, análisis sintáctico, análisis semántico. Dentro de la fase de Síntesis existen las etapas de generación de código intermedio, optimización de código intermedio, y generación de código. Al tener que describir como funciona un compilador tenemos que tener en claro en no confundir los términos compilador y compilación, se debe entender que al decir compilador nos referimos al programa, y al decir compilación al proceso en sí. En esencia ambos términos cumplen con la definición más simple de un compilador, es decir, sea el programa compilador o el proceso de compilación, ambos reciben como entrada un código escrito en algún lenguaje y producen como salida otro código escrito en otro lenguaje.
1.2. 1.2. HISTO HISTORIA RIA En 1946 se desarrolló el primer ordenador digital. En un principio, estas máquinas ejecutaban instrucciones que consistían en códigos numéricos que señalan a los circuitos de la máquina los estados correspondientes a cada operación. 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. Esta Estass clav claves es cons consti titu tuye yenn los los ll llam amad ados os leng lengua uaje jess ensa ensamb mbla lado dore res, s, que que se generalizaron 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. Las instrucciones de los lenguajes ensambladores obligan a programar cualquier funció funciónn de una manera manera min minuci uciosa osa e it itera erativ tiva. a. A pesar pesar de ello, ello, el lengu lenguaj ajee ensamblador seguía siendo el de una máquina, pero más fácil de manejar. Los trabajos de investigación se orientaron entonces hacia la creación de un lenguaje que expresara las distintas acciones a realizar de una manera lo más sencilla posible para el hombre. Así, en 1950, desarrollaron el FORTRAN con el cual surgió así por primera vez el concepto de un traductor (como un programa que traducía un lenguaje a otro lenguaje). En el caso particular de que el lenguaje a traducir es un lenguaje de alto nivel y el lenguaje traducido de bajo nivel, se emplea el término compilador. Inicio del Compilador La tare tareaa de real realiz izar ar un comp compil ilad ador or no fue fue fáci fácil. l. El prim primer er comp compil ilad ador or de FORTRAN tardó 18 años en realizarse y era muy sencillo. El FORTRAN estaba muy influenciado por la máquina objeto en la que iba a ser implementado. Como un ejemplo de ello tenemos el hecho de que los espacios en blanco fuesen ignorados, debido a que el periférico que se utilizaba como entrada de programas (una lectora de tarjetas perforadas) no contaba correctamente los espacios en blanco. Paralelamente al desarrollo de FORTRAN en América América,, en Europa surgió una corriente más universitaria, que pretendía que la definición de un lenguaje fuese
independiente de la máquina y en donde los algoritmos se pudieran expresar de forma más simple. Esta corriente estuvo muy influida por los trabajos sobre gramáticas de contexto libre publicados por Chomsky. Chomsky. Con estas ideas surgió un grupo europeo los cuales pidieron colaboración a la asociación americana y se formó un comité elcual desarrolló el lenguaje llamado ALGOL 58. En 1969, el lenguaje fue revisado y llevó a una nueva versión que se llamó ALGOL 60 y posteriormente ALGOL 68. Avances en Compilación Junto a este desarrollo en los lenguajes (ALGOL), también se iba avanzando en la técnica de compilación. En 1958 proponían una solución al problema de que un compilador fuera utilizable por varias máquinas objeto. Para ello, se dividía por primera vez el compilador en dos fases, designadas como el "front end" y el "back end". La primera fase (front end) es la encargada de analizar el programa fuente y la segunda fase (back end) es la encargada de generar código para la máquina objeto. El puente de unión entre las dos fases era un lenguaje intermedio que se designó con el nombre de UNCOL (UNiversal Computer Oriented Language). Para que un compilador fuera utilizable por varias máquinas bastaba únicamente modificar su back end. Aunque se hicieron varios intentos para definir el UNCOL, el proyecto el proyecto se ha quedado simplemente en un ejercicio teórico. De todas formas, la división de un compilador en front end y back end fue un adelanto importante. Bases de las Tareas en un Compilador En los años 1958 y 1959 se van poniendo las bases para la división de tareas en un compilador. Así, en 1959 se propone el empleo de autómatas deterministas y no deterministas para el reconocimiento lexicográfico de los lenguajes. Rápidamente se aprecia que la construcción de analizadores léxicos a partir de expresiones regulares es muy útil en la implementación de los compiladores. En 1975 surge el conc concep epto to de un gene genera rado dorr auto automá máti tico co de anal analiz izad ador ores es léxi léxico coss a part partir ir de expresiones regulares, basado en el sistema operativo UNIX. A partir de los trabajos de Chomsky ya citados, se produce una sistematización sistematización de la sintaxis de los lenguajes de programación, y con ello un desarrollo de diversos métodos de análisis sintáctico.
Con la aparición de la notación BNF, se tiene una guía para el desarrollo del análisis sintáctico.
1959 1959:: Se desc descri ribe be un méto método do de pars parsin ingg de FO FOR RTRAN TRAN que que intr introd oduc ucía ía paréntesis adicionales alrededor de los operandos para ser capaz de analizar las expresiones.
1960 1960:: Se desa desarro rroll llan an los los dive divers rsos os méto método doss de pars parser erss asce ascend nden ente tess y descendentes
1961: Se realiza el uso por primera vez de un parsing descendente recursivo.
1965: Se define las gramáticas LR y describe la construcción de una tabla canónica de parser LR.
1968 1968:: Se estu estudi dian an y defi define nenn las las gram gramát átic icas as LL así así como como los los pars parser erss predictivos.
1970: Se describen los métodos SLR y LALR de parser LR. Debido a su sencillez y a su capacidad de análisis para una gran variedad de lenguajes, la técnica de parsing LR va a ser la elegida para los generadores automáticos de parsers.
1975: Se crea el generador de analizadores sintácticos YACC para funcionar ba bajo un ento entorn rnoo UNIX UNIX . Junt Juntoo al anál anális isis is sint sintác ácti tico co,, tamb tambié iénn se fue fue desarrollando el análisis semántico.
En los primeros lenguajes (FORTRAN y ALGOL 60) los tipos posibles de los datos eran muy simples, y la comprobación de tipos era muy sencilla. No se permitía la corrección de tipos, pues ésta era una cuestión difícil. Con la aparición del ALGOL 68 se permitía que las expresiones de tipo fueran construidas sistemáticamente. sistemáticamente. Más tarde, de ahí surgió la equivalencia equivalencia de tipos por nombre y estructural. También se desarrollaron estrategias para mejorar las rutinas de entrada y de salida de un procedimiento . Así mismo, y ya desde los años 60, se estudió el paso de parámetros a un procedimiento un procedimiento por nombre, valor valor yy variable. Optimización en la Compilación La técnica de la optimización apareció desde el desarrollo del primer compilador de FORTRAN. Backus comenta cómo durante el desarrollo del FORTRAN se tenía el miedo de que el programa resultante de la compilación fuera más lento
que si se hubie hubiera ra escri escrito to a mano. mano. Para Para evitar evitar esto, esto, se intro introduj dujero eronn algun algunas as opti optimi miza zaci cion ones es en el cálc cálcul uloo de los los índi índice cess dent dentro ro de un bucl bucle. e. Pron Pronto to se sistematizan y se recoge la división de optimizaciones independientes de la máquina y dependientes de la máquina.
Entre las primeras están la propagación de valores , el arreglo de expresiones, la eliminación de redundancias, etc.
Entre las segundas se podría encontrar la localización de registros, el uso de instrucciones propias de la máquina y el reordenamiento de código.
A partir de 1970 comienza el estudio sistemático de las técnicas del análisis de flujo de datos. Su repercusión ha sido enorme en las técnicas de optimización global de un programa. Compilación en la Actualidad En la actu actual alid idad ad,, el proc proces esoo de la comp compil ilac ació iónn ya está está mu muyy asen asenta tado do.. Un compilador es una herramienta bien conocida, dividida en diversas fases. Algunas Algunas de estas fases se pueden generar automáticamente (analizador léxico y sintáctico) y otras requieren una mayor atención por parte del escritor de compiladores (las partes de traducción y generación de código). De todas formas, y en contra de lo que quizá pueda pensarse, todavía se están llevando a cabo varias vías de investigación en este fascinante campo de la compilación:
Por una parte, se están mejorando las diversas herramientas disponibles (por ejemp ejemplo, lo, el gener generado adorr de analiz analizado adores res léxico léxicoss Aardva Aardvark rk para para el lengua lenguaje je PASCAL).
También la aparición de nuevas generaciones de lenguajes -ya se habla de la quint qui ntaa genera generació ción, n, como como de un lengu lenguaj ajee cerca cercano no al de los human humanosos-ha ha provocado la revisión y optimización de cada una de las fases del compilador.
El último lenguaje de programación de amplia aceptación que se ha diseñado, el lenguaje Java, establece que el compilador no genera código para una máquina dete determi rmina nada da sino sino para para una una virt virtua ual, l, la Java Java Virtu irtual al Mach Machin inee (JVM (JVM), ), que que posteriormente será ejecutado por un intérprete, normalmente incluido en un navegador de Internet. El gran objetivo de esta exigencia es conseguir la máxima portabilidad de los programas escritos y compilados en Java, pues es únicamente
la segunda fase del proceso la que depende de la máquina concreta en la que se ejecuta el intérprete.
Gráfico Nº 1: Historia de la compilación 1.3. CONCEPTO CONCEPTOS S BÁSICOS BÁSICOS
Traductor. Traductor. Cualquier programa que toma como entrada un texto escrito en un lenguaje llamado fuente y da como salida un programa equivalente en otro lenguaje, el lenguaje objeto. Si el lenguaje fuente de un lenguaje de programación de alto nivel y el objeto un lenguaje de bajo nivel (ensamblador o código de máquina), al traductor se le denomina compilador. compilador.
Ensamblador. Es un programa traductor cuyo lenguaje fuente es el lenguaje ensamblador.
Gráfico Nº 2: Ensambladores Ensambladores
Intérprete. Es un programa que no genera un programa equivalente, sino que toma una sentencia del programa fuente en un lenguaje de alto nivel y la traduce al código equivalente y al mismo tiempo lo ejecuta. En un prin princi cipi pioo debi debido do a la esca escase sezz de memo memori riaa se util utiliz izab aban an más más los los intérpretes, ahora se usan más los compiladores (a excepción de JAVA)
1.4. CONCEPTO CONCEPTO DE COMPIL COMPILADOR ADOR En el caso de que el lenguaje fuente sea un lenguaje de programación de alto nivel y el objeto sea un lenguaje de bajo nivel (ensamblador o código de máquina), a dicho traductor se le denomina compilador. Un ensamblador es un compilador cuyo lenguaje fuente es el lenguaje ensamblador. Históricamente, con la escasez de memoria de los primeros ordenadores, se puso de moda el uso de intérpretes frente a los compiladores, pues el programa fuente sin traducir y el intérprete juntos daban una ocupación de memoria menor que la resultante de los compiladores. Por ello los primeros ordenadores personales iban siempre acompañados de un intérprete de BASIC (Spectrum, Commodore VIC20, PC XT de IBM, etc.). La mejor información sobre los errores por parte del compilador así como una mayor velocidad de ejecución del código resultante hizo que poco a poco se impusieran los compiladores. Hoy en día, y con el problema de la memoria prá práct ctic icam amen ente te resu resuel elto to,, se pued puedee habl hablar ar de un gran gran pred predom omin inio io de los los compiladores compiladores frente a los intérpretes, aunque intérpretes intérpretes como los incluidos en los navegadores de Internet para interpretar el código JVM de Java son la gran excepción. La razón principal para querer usar un compilador es querer traducir un pro progr gram amaa de un leng lengua uaje je de alto alto nive nivel, l, a otro otro leng lengua uaje je de nive nivell infe inferi rior or (típicamente lenguaje máquina). máquina ). De esta manera un programador puede diseñar un programa en un lenguaje mucho más cercano a como piensa un ser humano, para luego compilarlo a un programa más manejable por una computadora.
Programa fuente
Compilador
Programa objeto
Mensaje de error
Gráfico Nº 3: Resultado de la compilación 1.5. INTERPRETAR INTERPRETAR VS COMPILAR COMPILAR Hay dos maneras de ejecutar un programa escrito en un lenguaje de alto nivel
Compilación: traducir todo el programa a otro programa equivalente en código máquina. Entonces se ejecuta el programa obtenido
Interpretación: interpretar las instrucciones del programa escrito en lenguaje de alto nivel y ejecutarla una por una.
Ventajas de compilar frente a interpretar:
Se compila una vez, se ejecuta n veces.
En bucl bucles es,, la comp compil ilac ació iónn gene genera ra códi código go equi equiva vale lent ntee al bucl bucle, e, pero pero interpretándolo se traduce tantas veces una línea como veces se repite el bucle.
El compilador tiene una visión global del programa, por lo que la información de mensajes de error es más detallada.
Ventajas del intérprete frente al compilador:
Un intérprete necesita menos memoria que un compilador. En principio eran más abundantes dado que los ordenadores tenían poca memoria.
Permiten una mayor interactividad con el código en tiempo de desarrollo.
Un compilador no es un programa que funciona de manera aislada, sino que necesita de otros programas para conseguir su objetivo: obtener un programa ejecutable ejecutable a partir de un programa fuente en un lenguaje de alto nivel. Algunos de esos programas son el preprocesador, el linker, el depurador y el ensamblador.
El preprocesador se ocupa (dependiendo del lenguaje) de incluir ficheros, expandir macros, eliminar comentarios, y otras tareas similares.
El linker (Enlazador) se encarga de construir el fichero ejecutable ejecutable añadiendo al fichero objeto generado por el compilador las cabeceras necesarias y las funciones de librería utilizadas por el programa fuente.
El depurado depuradorr permi permite, te, si el compi compilad lador or ha genera generado do adecu adecuada adamen mente te el programa objeto, seguir paso a paso la ejecución de un programa. Finalmente,
Muchos compiladores, en vez de generar código objeto, generan un programa en lengu lenguaj ajee ensam ensambla blador dor que debe debe despué despuéss conve converti rtirse rse en un ejecu ejecutab table le mediante un programa ensamblador. ensamblador.
Gráfico Nº 4: Diagrama a bloques de la operación de un buen compilador. compilador.
1.6. TIPOS TIPOS DE COMPILAD COMPILADORES ORES El programa compilador traduce las instrucciones en un lenguaje de alto nivel a instrucciones que la computadora puede interpretar y ejecutar. Para cada lenguaje de programación se requiere un compilador separado. El compilador traduce todo el progra programa ma antes antes de ejecu ejecutar tarlo. lo. Los compi compilad ladore oress son, son, pues, pues, progra programas mas de traducción insertados en la memoria por el sistema operativo para convertir progr programa amass de cómpu cómputo to en pul pulsac sacion iones es electr electróni ónicas cas ejecut ejecutab ables les (lengu (lenguaj ajee de máquina). Los compiladores pueden ser de:
una sola pasada: examina el código fuente una vez, generando el código o programa objeto.
pasadas múltiples: requieren pasos intermedios para producir un código en otro otro leng lengua uaje je,, y una una pasa pasada da fina finall para para prod produc ucir ir y opti optimi miza zarr el códi código go producido durante los pasos anteriores.
Optimación: lee un código fuente, lo analiza y descubre errores potenciales sin ejecutar el programa.
Compiladores Compiladores incrementales: incrementales: genera generann un códig códigoo obje objeto to instru instrucci cción ó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 lengua lenguaje je fuente fuente es lengu lenguaj ajee ensamb ensamblad lador or y posee 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. compilar. Evidentemente, no se puede ejecutar la primera vez. Sirve para hacer ampliaciones al lenguaje, mejorar el código generado, etc.
Metacompilador: Metacompilador: es sinónimo de compilador de compiladores y se refiere a un programa que recibe como entrada las especificaciones 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 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 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. Esta taxonomía de los tipos de compiladores no es excluyente, por lo que puede haber compiladores que se adscriban a varias categorías:
Compiladores cruzados: generan código para un sistema distinto del que están funcionando.
Compiladores Compiladores optimizadores: optimizadores: realizan cambios en el código para mejorar su eficiencia, pero manteniendo la funcionalidad del programa original.
Compiladores Compiladores de una sola pasada: generan el código máquina a partir de una única lectura del código fuente.
Compiladores Compiladores de varias pasadas: necesitan leer el código fuente varias veces antes de poder producir el código máquina.
Compiladores JIT (Just In Time): forman parte de un intérprete y compilan partes del código según se necesitan.
CAPÍTULO II: CONCEPTOS FUNDAMENTALES DE LOS COMPILADORES 2.1. ESTRUCTURA DE UN COMPILADOR. Cualquier compilador debe realizar dos tareas principales: análisis del programa a compilar y síntesis de un programa en lenguaje maquina que, cuando se ejecute, realizara correctamente las actividades descritas en el programa fuente. Para el estudio de un compilador, es necesario dividir su trabajo en fases. Cada fase representa una transformación al código fuente para obtener el código objeto. La siguiente figura representa los componentes en que se divide un compilador. Las tres primeras fases realizan la tarea de análisis, y las demás la síntesis. En cada una de las fases se utiliza un administrador de la tabla de símbolos y un manejador de errores.
Gráfico Nº 5: Estructura de un compilador
2.1.1.PREPROCESADOR Es el encargado de transformar el código fuente de entrada original en el código fuente puro. Es decir en expandir las macros, incluir las librerías, realizar un preprocesado racional (capacidad de enriquecer a un lenguaje antiguo con recursos más modernos), extender el lenguaje y todo aquello que en el código de entrada sea representativo de una abreviatura para facilitar la escritura del mismo.
2.1.2.COMPILACIÓN Recibe el código fuente puro, este es él modulo principal de un compilador, pues si ocurriera algún error en esta etapa el compilador no podría avanzar. avanzar. En esta etapa se somete al código fuente puro de entrada a un análisis léxico gráfico, a un análisis sintáctico, a un análisis semántico, que construyen la tabla de símbolos, se genera un código intermedio al cual se optimiza para así pod poder er prod produc ucir ir un códi código go de sali salida da gene genera ralm lmen ente te en algú algúnn leng lengua uaje je ensamblador.
2.1.3.ENSAMBLADO Este modulo no es ni más mi menos que otro compilador pues recibe un código fuente de entrada escrito en ensamblador, y produce otro código de salida, llamado código binario no enlazado. Si por un momento viéramos a
este modulo como un programa independiente, veríamos que en este caso los términos programa compilador y proceso de compilación son los mismos. Pues este modulo no es mas que un compilador, que en su interior realiza como su antecesor un análisis léxico gráfico, un análisis sintáctico, un análisis semán semántic tico, o, crea crea una tabla tabla de símbol símbolos, os, gener generaa un códig códigoo int interm ermedi edioo lo optimiza y produce un código de salida llamado código binario no enlazado, y a todo este conjunto de tares se los denomina proceso de compilación. Como se puede ver este compilador (llamado ensamblador) ensamblador) a diferencia de los demás compilad compiladores ores no realiza realiza una expansió expansiónn del código código fuente fuente original( original(códi código go fuente de entrada), tiene solamente un proceso de compilación y por supuesto no enlaza el código fuente. Es un compilador que carece de los módulos de preprocesado y enlazado, y donde los módulos de compilación y ensamblado son los mismos.
2.1.4.ENLAZADO El cuarto y ultimo modulo es el encargado de realizar el enlazado del código de fuente de entrada (código maquina relocalizable) con las librerías que necesita, como así también de proveer al código de las rutinas necesarias para poder ejecutarse y cargarse a la hora de llamarlo para su ejecución, modifica las direcciones relocalizables relocalizables y ubica los datos en las posiciones apropiadas de la memoria. Este ultimo modulo es el que produce como salida el código binario enlazado. Ya sea dinámico o estático, al decir dinámico se refiere a que el código producido utiliza librerías dinámicas (librerías ya cargadas en el sist sistem ema) a),, esto esto im impl plic icaa que que se obte obtend ndrá rá un códi código go más más cort cortoo y que que se actualizara automáticamente si aparece alguna nueva versión de las librerías, mientras que el estático se refiere al echo que no se realiza r ealiza enlace con ninguna librería y por lo tanto se obtendrá un código mas largo con una copia de las rutinas de librería que necesita. necesita.
Biblioteca (Archivos objeto relocalizables)
2.2. PROCESO PROCESO DE COMPILACI COMPILACIÓN ÓN
Gráfico Nº 6: estructura del proceso de compilación
Analizando en detalle el proceso de compilación, se divide en dos grandes fases:
Fase de Análisis
Análisis Léxico
Análisis Sintáctico
Análisis Semántico
Fase de Síntesis
Etapa de Generación de Código Intermedio I ntermedio
Etapa de Optimización de Código
Etapa de Generación de Código
2.2.1.FASE DE ANÁLISIS En esta fase se crea una representación intermedia de código
Análisis léxico (scanner). En la fase de análisis léxico se leen los caracteres del programa fuente y se agrupa agrupann en cadena cadenass que repres represent entan an los compon componen entes tes léxic léxicos. os. Cada Cada componente léxico es una secuencia lógicamente coherente de caracteres relat relativa iva a un ident identif ifica icador dor,, una palabr palabraa reserv reservada ada,, un operad operador or o un carácter de puntuación. A la secuencia de caracteres que representa un componente léxico se le llama lexema (o con su nombre en inglés token). En el caso de los identificadores creados por el programador no solo se genera un componente léxico, sino que se genera otro lexema en la tabla de símbolos.
Análisis sintáctico (parser). En esta fase, los componentes léxicos se agrupan en frases gramaticales que el compilador utiliza para sintetizar la salida.
Análisis semántico. La fase de análisis semántico se intenta detectar instrucciones que tengan la estructura sintáctica correcta, pero que no tengan significado para la operación implicada.
2.2.2.FASE DE SÍNTESIS Genera un código a partir de la representación representación intermedia
Generación de código intermedio. Algunos compiladores generan una representación representación intermedia explícita del programa fuente, una vez que se han realizado las fases de análisis. Se puede considerar esta operación intermedia como un subprograma para una máquina abstracta. Esta representación intermedia debe tener dos propiedades importantes: debe ser fácil de producir y fácil de traducir al programa objeto.
Optimización de código. En esta fase se trata de mejorar el código intermedio, de modo que resulte un código de máquina más rápido de ejecutar.
Generación de código. Esta constituye la fase final de un compilador. En ella se genera el código objeto que por lo general consiste en código en lenguaje máquina (código relocalizable) o código en lenguaje ensamblador.
Además existen:
Administrador de la tabla de símbolos. Una tabla de símbolos es una estructura de datos que contiene un registro por cada identificador. identificador. El registro incluye los campos para los atributos del identificador. El administrador de la tabla de símbolos se encarga de manejar los accesos a la tabla de símbolos, en cada una de las etapas de compilación compilación de un programa.
Manejador de errores. En cada fase del proceso de compilación es posibles encontrar errores. Es conv conven enie ient ntee que que el trat tratam amie ient ntoo de los los erro errore ress se haga haga de mane manera ra centralizada a través de un manejador de errores. De esta forma podrán controlarse más eficientemente los errores encontrados en cada una de las fases de la compilación de un programa.
2.3. EJEMPLO DEL DEL PROCESO DE COMPILACIÓN Supongamos que un compilador tiene que analizar la siguiente preposición:
suma= var1 + var2 + 10 Análisis Léxico El analiz analizado adorr léxic léxicoo lee los carac caracter teres es del del progra programa ma fuente fuente,, y verifi verifica ca que correspondan a una secuencia lógica (identificador, palabra reservada etc.). Esta secuencia de caracteres recibe el nombre componente léxico o lexema. En este caso el analizador léxico verifica si el identificador id1 (nombre interno para "suma") "suma") encontra encontrado do se halla halla en la tabla tabla de símbolos, símbolos, si no esta produce produce un error porque todavía no fue declarado, si la preposición hubiese sido la declaración del identi identific ficado adorr "suma" "suma" en lengu lenguaje ajess C, C++ C++ (int (int suma; suma;)) el analiz analizado adorr léxic léxicoo agregaria un identificador en la tabla de símbolos, y así sucesivamente con todos los componentes léxicos que aparezcan, los componentes léxicos resultantes de la expresión son: Identificador:
suma.
El símbolo de asignación:
=
Identificador:
var1
Operador:
+
Identificador:
var2
Operador:
+
Numero:
10
Que en el análisis léxico y con la tabla de símbolos es: id1= id2+ id3 * 10 Análisis Sintáctico El anal analiz izad ador or sint sintác ácti tico co im impo pone ne una una estr estruc uctu tura ra jerá jerárq rqui uica ca a la cade cadena na de componentes léxicos, generada por el analizador léxico, que es representada en forma de un árbol sintáctico.
= / \ id1 + / \ id2 + / \ id3 10 Análisis Semántico El analizador semántico verificara en este caso que cada operador tenga los operandos permitidos. = / \ id1 + / \ id2 + / \ id3 tipo_ent | 10 Generador de código intermedio En esta etapa se lleva la preposición a una representación intermedia como un programa para una maquina abstracta. abstracta. temp1= tipo_ent(10) temp2= id3 * temp1 temp3= id2 + tem2 id1= temp3 Optimización de código El código intermedio obtenido es representado de una forma mas optima y eficiente. temp1= id3 * 10.0 id1= id2 + temp1 Generador de código Finalmente lleva el código intermedio a un código objeto que en este caso es un código relocalizable relocalizable o código ensamblador (también llamado código no enlazado). MOVF id3, R2 MULT #10.0, R2
MOVF id2, R1 ADDF R2, R1 MOVF R1, id1 Este el código objeto obtenido que es enviado al modulo de ensamblado. Para entender todo esto veamos un ejemplo utilizando utilizando como lenguaje en este caso al popular lenguaje de programación C creado por Kernighan y Ritchie. El siguiente código esta definido de acuerdo al standard ANSI C. #include void main() { char* frase= " Hola Mundo...!!!"; Mundo...!!!"; printf("%s", printf("%s", frase ); };
En la primer línea se encuentra una directiva de preprocesador, esta línea le indica al compilador que tiene que incluir la librería stdio.h, es decir transformar el código fuente de entrada en el código fuente puro (expandido). Al pasar por el modulo de preprocesado, el código fuente queda de la siguiente manera. # 1 "hmundo.c" # 1 "c:/compilador/include/ "c:/compilador/include/stdio.h" stdio.h" 1 3 # 1 " c:/compilador/include/sys/type c:/compilador/include/sys/types.h" s.h" 1 3 # 12 " c:/compilador/include/stdio.h" 2 3 typedef void *va_list; typedef long unsigned int size_t; typedef struct { int _cnt; char *_ptr; char *_base; int _bufsiz; int _flag; int _file; char *_name_to_remove; *_name_to_remove; } FILE; typedef unsigned long fpos_t; extern FILE __stdin, __stdout, __stderr; void clearerr(FILE *_stream); int fclose(FILE *_stream); int feof(FILE *_stream); int ferror(FILE *_stream); int fflush(FILE *_stream); int fgetc(FILE *_stream); int fgetpos(FILE *_stream, fpos_t *_pos); char * fgets(char *_s, int _n, FILE *_stream); FILE * fopen(const char *_filename, const char *_mode); int fprintf(FILE *_stream, const char *_format, ...); int fputc(int _c, FILE *_stream); int fputs(const char *_s, FILE *_stream);
size_t fread(void *_ptr, size_t _size, size_t _nelem, FILE *_stream); FILE * freopen(const char *_filename, const char *_mode, FILE *_stream); int fscanf(FILE *_stream, const char *_format, ...); int fseek(FILE *_stream, long _offset, int _mode); int fsetpos(FILE *_stream, const fpos_t *_pos); long ftell(FILE *_stream); size_t fwrite(const void *_ptr, size_t _size, size_t _nelem, FILE *_stream); int getc(FILE *_stream); int getchar(void); getchar(void); char * gets(char *_s); void perror(const char *_s); int printf(const char *_format, ...); int putc(int _c, FILE *_stream); int putchar(int _c); int puts(const char *_s); int remove(const char *_filename); *_filename); int rename(const char *_old, const char *_new); void rewind(FILE *_stream); int scanf(const char *_format, ...); void setbuf(FILE *_stream, char *_buf); int setvbuf(FILE *_stream, char *_buf, int _mode, size_t _size); int sprintf(char *_s, const char *_format, ...); int sscanf(const char *_s, const char *_format, ...); FILE * tmpfile(void); char * tmpnam(char *_s); int ungetc(int _c, FILE *_stream); int vfprintf(FILE *_stream, const char *_format, va_list _ap); int vprintf(const char *_format, va_list _ap); int vsprintf(char *_s, const char *_format, va_list _ap); int fileno(FILE *_stream); FILE * fdopen(int _fildes, const char *_type); int pclose(FILE *_pf); FILE * popen(const char *_command, const char *_mode); extern FILE _stdprn, _stdaux; void _stat_describe_lossage(FILE *_to_where); int _doprnt(const char *_fmt, va_list _args, FILE *_f); int _doscan(FILE *_f, const char *_fmt, void **_argp); int _doscan_low(FILE *, int (*)(FILE *_get), int (*_unget)(int, FILE *), const char *_fmt, void **_argp); int fpurge(FILE *_f); int getw(FILE *_f); int mkstemp(char *_template); char * mktemp(char *_template); int putw(int _v, FILE *_f); void setbuffer(FILE *_f, void *_buf, int _size); void setlinebuf(FILE *_f); char * tempnam(const char *_dir, const char *_prefix); int _rename(const char *_old, const char *_new); # 1 "hmundo.c" 2 void main() { char* frase= " Hola Mundo...!!!"; Mundo...!!!"; printf("%s", printf("%s", frase ); };
El nuevo código contiene el encabezado o prototipo de la/s función/es que se encuentran en el archivo de cabecera stdio.h, y que serán posiblemente utilizadas
en el código fuente original. Este código es pasado al modulo de compilación quien luego de analizarlo y verificar si se encuentra correcto, transformara el código fuente puro (expandido) en código ensamblador y lo envía al modulo de ensamblado. .file "hmundo.c" compiler_compiled.: ___compiled_c: .text LC0: .ascii " Hola Mundo...!!!\0" LC1: .ascii "%s\0" .align 2 .globl _main _main: pushl %ebp movl %esp,%ebp subl $4,%esp call ___main movl $LC0,-4(%ebp) movl -4(%ebp),%eax pushl %eax pushl $LC1 call _printf addl $8,%esp L1: leave ret
Este código será analizado por él modulo de ensamblado, que lo llevara a código binario no enlazado, y lo enviara al modulo de enlazado. El código de salida enviado al modulo de enlazado es el siguiente. L³Ú(7ô.text @ŒÌ ............. .data@@@ .bss@@ Hola Mundo...!!!%s ヘ v U‰åƒìèÝÿÿÿÇEü‹EüPh èÈÿÿÿƒÄ Éà ヘ v.fileþÿ ghmundo.c. ............. _main ___main _printf% _compiled. ___compiled_c
Finalmente él modulo de enlazado, vincula el código binario sin enlazar con las librerías dinámicas necesarias según sea el caso o no. Y produce como salida el código binario enlazado o código binario ejecutable.
MZ 'ÿÿ`T 'ÿÿ`T 0; $Id: xxx.asm built mm/dd/aa 00:00:00 by ...asm $ @(#) xxx.asm built mm/dd/aa 00:00:00 by ...asm ............. ]ヘ v Hola Mundo...!!!%s ヘ v U‰åƒìèý ÇEüx"‹EüPhŠ"è, ÇEüx"‹EüPhŠ"è, ƒÄ Éà ヘ v387 No 80387 detected. Warning: Coprocessor Coprocessor not present and DPMI setup failed! If application attempts floating operations system may hang! ¸'Éà ヘ v¸"ÉÃCall frame traceback EIPs: 0x 0x Alignment CheckCoprocessor ErrorPage faultGeneral Protection FaultStack FaultStack FaultSegment FaultSegment Not PresentInvalid PresentInvalid TSSCoprocessor TSSCoprocessor overrunDouble ............ Division by Zero: sel= invalid base= limit= ヘ vU‰åƒìS‹]jÿu vU‰åƒìS‹]jÿu jè? ............
Este es el código final conocido como código maquina. 2.4. HERRAMIENTAS HERRAMIENTAS PARA PARA CONSTRUCCIÓN DE COMPILADORES COMPILADORES
Sistema de ayuda para escribir compiladores
Generadores de compiladores
Sist. Generadores de traductores
Herra Herramie mienta ntass gener generale aless pa para ra el diseño diseño automá automátic ticoo de compo compone nente ntess específicos de un comp.
Util Utiliz izan an leng lengua uaje jess espe especí cífi fico coss para para espe especi cifi fica carr e im impl plem emen enta tarr la componente
Ocultan detalles del algoritmo de generación
Producen componentes que se pueden integrar al resto del compilador
Generadores Generadores de analizadores analizadores sintácticos
Producen AS AS a partir de una Gramática Libre de Contexto
Hoy esta es una de las fases más fáciles de aplicar
Generadores de analizadores léxicos
Producen AL a partir de una especificación en Expresiones Regulares.
El AL resultante es un Autómata Finito
Dispositivos de traducción dirigida por la sintaxis
Producen grupos de rutinas que recorren el árbol de AS generando código intermedio
Generadores Generadores automáticos de código
Las proposiciones en cod. Int. se reemplazan por plantillas plantillas que representan secuencia de instrucciones de máquina
Dispositivos para análisis de flujo de datos
Inf. sobre como los valores valores se transmiten transmiten de una parte a otra del programa
LEX y YACC
Herramientas que nos permiten desarrollar componentes o la mayor parte de un compilador
Son un recurso invaluable para el profesional y el investigador
Existen paquetes freeware
2.5. EL LENGUAJE LENGUAJE Y LA HERRAMIENTA HERRAMIENTA Tabla Nº 1: lenguajes y la herramienta MODELO
Compilado
Interpretado
LENGUAJE
CARACTERÍSTICAS
Fortran, COBOL, C/C++, Si Sint ntax axis is rigu riguro rosa sa,, velo veloci cida dadd y Pascal
tamaño
Lisp Lisp,, AWK; WK; BASI BASIC; C; Desempeño lento, actividades no SQL
planeadas, sintaxis relajadas Trans ranspo port rtab abil ilid idad ad abso absolu luta ta,,
Pseu seudoco docom mpil pilado ado Jav Java
desempeño intermedio, sintaxis rigurosa
2.6. ASPECTOS ACADÉMICOS Y DE INVESTIGACIÓN INVESTIGACIÓN DE COMPILADORES Tabla Nº 2: Aspectos académicos de la investigación de compiladores
ÁREA Lenguaje de programación
BENEFICIOS Principios para su desarrollo Herramientas para la implementación implementación
Inteligencia artificial
Interfases de reconocimiento de lenguaje natural
Sistemas operativos
Desa Desarr rrol ollo lo de inte interf rfas ases es de cont contro roll y usuari usuarioo final final.. Interp Interpret retes es de coman comandos dos (Shell)
Diseño de interfases
Desa Desarr rrol ollo lo de inte interf rfas ases es orie orient ntad adas as a comando y carácter. Voz o escritura.
Administración de proyectos Selección de herramientas de desarrollo. Evaluación de costo de beneficio informáticos
CAPÍTULO III: ANÁLISIS LÉXICO 3.1. ANÁLISIS ANÁLISIS LÉXICO LÉXICO (SCANNER) (SCANNER) El analizador léxico, también conocido como scanner, scanner, lee los caracteres uno a uno desde la entrada y va formando grupos de caracteres con alguna relación entre sí (tokens), que constituirán la entrada para la siguiente etapa del compilador. Cada token representa una secuencia de caracteres que son tratados como una única entidad. Por ejemplo, en Pascal un token es la palabra reservada BEGIN, en C: WHILE, etc. Las tiras específicas sólo tienen tipo (lo que representan), mientras que las tiras no específicas tienen tipo y valor. Por ejemplo, si “Contador” es un identificador, el tipo de token será identificador identificador y su valor será la cadena “Contador”. El Analizador Léxico es la etapa del compilador que va a permitir saber si es un lenguaje de formato libre o no. Frecuentemente va unido al analizador sintáctico en la misma pasada, funcionando entonces como una subrutina de este último. Ya que es el que va leyendo los caracteres del programa, ignorará aquellos elementos innecesarios innecesarios para la siguiente fase, como los tabuladores, comentarios, espacios en blanco, etc. 3.2. EL PROCESO DEL ANÁLISIS LÉXICO LÉXICO El proceso de análisis léxico se refiere al trabajo que realiza el scanner con relación al proceso de compilación. El scanner representa una interfaz entre el programa fuente y el analizador sintáctico o parser. El scanner, a través del examen carácter por carácter del texto, separa el programa fuente en piezas llamadas tokens, los cuales representan los nombres de las variables, operadores, etiquetas, y todo lo que comprende el programa fuente. El parser, usualmente genera un árbol de sintaxis del programa fuente como ha sido definido por una gramática. Las hojas del árbol son símbolos terminales de la gramática. Son esos símbolos terminales o tokens los que el scanner extrae del código fuente y se los pasa al parser. Es posible para el parser usar el conjunto de caracteres terminales del lenguaje como el conjunto de tokens, pero ya que los tokens pueden ser definidos en términos de gramáticas regulares más simples que
en las gramáticas más complejas utilizadas por los parsers, es deseable usar scanners. Usar solo parsers es costoso en términos de tiempo de ejecución y requerimientos de memoria, y la complejidad y el tiempo de ejecución puede reducirse con el uso de un scanner. La separación entre análisis léxico (scanning) y análisis sintáctico (parsing) puede tener también otras ventajas. El análisis léxico de caracteres generalmente es lento en los compiladores, y separándolo del componente de análisis semántico de la compilación, el énfasis particular puede darse para hacer más eficiente el proceso. Un analizador de léxico tiene como función principal el tomar secuencias de caracteres o símbolos del alfabeto del lenguaje y ubicarlas dentro de categorías, conocidas como unidades de léxico. Las unidades de léxico son empleadas por el analizador gramatical para determinar si lo escrito en el programa fuente es corr correc ecto to o no gram gramat atic ical alme ment nte. e. Algu Alguna nass de las las unid unidad ades es de léxi léxico co no son son empleadas por el analizador gramatical sino que son descartadas o filtradas. Tal Tal es el caso de los comentarios, que documentan el programa pero que no tienen un uso gramatical, o los espacios en blanco, que sirven para dar legibilidad a lo escrito. En la terminología empleada en la construcción de un analizador de léxico se encuentran los siguientes términos. 3.3. ¿QUE ES UN ANALIZA ANALIZADOR DOR LÉXICO? LÉXICO? Se encarga de buscar los componentes léxicos ( tokens En ingles) o palabras que componen el programa fuente, según unas reglas o patrones. La entrada del analizador léxico podemos definirla como una secuencia de caracteres.
Gráfico Nº 7: Analizador léxico El analizador léxico tiene que dividir la secuencia de caracteres en palabras con significado propio y después convertirlo a una secuencia de terminales desde el punto de vista del analizador sintáctico, que es la entrada del analizador sintáctico.
El analizador léxico reconoce las palabras en función de una gramática regular de manera que sus NO TERMINALES se convierten en los elementos de entrada de fases posteriores. En LEX, por ejemplo, esta gramática se expresa mediante expresiones regulares. 3.4. FUNCIONES DEL DEL ANALIZADOR LÉXICO LÉXICO El analizador léxico es la primera fase de un compilador. Su principal función consiste en leer los caracteres de entrada y elaborar como salida una secuencia de componentes léxicos que utiliza el analizador sintáctico para hacer el análisis. Esta interacción, suele aplicarse convirtiendo al analizador léxico en una subrutina o corrut corrutina ina del del anali analizad zador or sintác sintáctic tico. o. Recibi Recibida da la orden orden “Dame “Dame el sigui siguient entee compon componen ente te léxic léxico” o” del analiz analizado adorr sintác sintáctic tico, o, el anali analizad zador or léxico léxico lee lee los caracteres de entrada hasta que pueda identificar el siguiente componente léxico. Estos componentes léxicos representan:
palabras reservadas: if, while, do, . . .
identicadores: asociados a variables, nombres de funciones, tipos definidos por el usuario, etiquetas,... etiquetas,... Por ejemplo:
Forma: una letra seguida de letras o números. Ej. a, b1, c3D
Atributo nombre: string con la secuencia de caracteres que forma el identificador en mayúsculas. Ej. “A”, “B1”, “C3D”
operadores: = * + - / == > < &! = . . .
símbolos especiales: ; ( ) [ ] f g ...
constantes numéricas: literales que representan valores enteros, en coma flotante, etc, 982, 0xF678, -83.2E+2,...
Forma: secuencia de dígitos que puede empezar con el signo menos y puede contener un punto. Ej. 10, -3, 15.4, -54.276, .10
Atributo valor: Double con el valor numérico.
Precisión: entero o real.
constante constantess de caracter caracteres: es: literales que representan cadenas concretas de caracteres, \hola mundo",...
Gráfico Nº 8: Interacción de un analizador léxico con el analizador sintáctico Otras funciones que realiza:
Eliminar los comentarios del programa.
Eliminar espacios espacios en blanco, tabuladores, tabuladores, retorno de carro, carro, etc, y en general, todo aquello que carezca de significado según la sintaxis del lenguaje.
Reconocer los identificadores de usuario, números, palabras reservadas del lenguaje,..., y tratarlos correctamente con respecto a la tabla de símbolos (solo en los casos que debe de tratar con la tabla de símbolos).
Llevar la cuenta del número de línea por la que va leyendo, por si se produce algún error, dar información sobre donde se ha producido.
Avisar de errores léxicos. Por ejemplo, si @ no pertenece al lenguaje, avisar de un error.
Puede hacer funciones de preprocesador.
3.5. NECESIDAD DEL DEL ANALIZADOR LÉXICO LÉXICO Un tema importante es el porqué se separan los dos análisis lexicográfico y sintáctico, en vez de realizar sólo el análisis sintáctico, del programa fuente, cosa perfectamente posible aunque no plausible. Algunas razones de esta separación son:
Un diseño sencillo es quizás la consideración más importante. Separar el análisis léxico del análisis sintáctico a menudo permite simplificar una u otra de dichas fases. El analizador léxico nos permite simplificar el analizador sintáctico.
Gráfico Nº 9: Necesidad del analizador léxico Si el sintáctico tuviera la gramática de la Opción 1 , el lexicográfico sería: Opción 1: ( 0 | 1 | 2 | ... | 9) +
(“+” | “-” | ”*“ | ”/“)
NUM OPARIT
Si en cambio el sintáctico toma la Opción 2, el lexicográfico sería: Opción 2: ( 0 | 1 | 2 | ... | 9) +
NUM
“+”
MAS
“-”
MENOS
“*”
MULT
“/”
DIV
Es más, si ni siquiera hubiera análisis léxico, el propio análisis sintáctico vería incrementado incrementado su número de reglas: NUM
0
|1 |2 |3 .... | NUM NUM
3.6. 3.6. VENT VENTAJ AJAS AS DE SEP SEPARAR ARAR EL ANÁL ANÁLIS ISIS IS LÉXI LÉXICO CO Y EL ANÁLIS ANÁLISIS IS SINTÁCTICO:
Facil Facilita ita transp transport ortabi abili lidad dad del traduc traducto torr (por (por ejemp ejemplo, lo, si decidi decidimos mos en un momento dado cambiar las palabras reservadas begin y end de inicio y en de bloque, por f y g, solo hay que cambiar este modulo.
Se simplifica el diseño: el analizador es un objeto con el que se interactúa mediante ciertos métodos. Se localiza en un único modulo la lectura física de los caracteres, por lo que facilita tratamientos especializados de E/S.
Se mejora la eficiencia del compilador. Un analizador léxico independiente permite construir un procesador especializado y potencialmente más eficiente para esa función.
Gran parte del tiempo se consume en leer el programa fuente y dividirlo en componentes léxicos. Con técnicas especializadas de manejo de buffers para la lectura de caracteres de entrada y procesamiento de componentes léxicos se puede mejorar significativamente el rendimiento de un compilador.
Otra razón por la que se separan los dos análisis es para que el analizador léxico se centre en el reconocimiento de componentes básicos complejos.
3.7. COMPONENTES LÉXICOS, LÉXICOS, PA PATRONES, LEXEMAS 3.7.1.COMPONENTE 3.7.1.COMPONENTE LÉXICO O TOKEN El valor asociado a una categoría o unidad de léxico. Se representa como un núme número ro ente entero ro o una una cons consta tant ntee de un byte byte.. Ejem Ejempl plo: o: el toke tokenn de un identificador identificador puede ser 1 ó id (si id fue definida como 1). Los tokens son las unidades léxicas básicas de igual forma que las palabras y signos de puntuación son las unidades básicas de un enunciado. Los tokens varían del lenguaje al lenguaje e incluso de compilador a compilador para el mism mi smoo leng lengua uaje je,, la elec elecci ción ón de los los toke tokens ns es tare tareaa del del dise diseña ñado dorr del del compilador. En la mayoría de lenguajes tendremos tokens para :
palabras clave: IF THEN THEN THEN = ELSE; ELSE ELSE = THEN;
operadores
identificadores
constantes (reales, enteras y de tipo carácter), strings de caracteres y signos de puntuación
1. Tipos de tokens:
tiras específicas, tales como palabras reservadas (if, while, begin, etc.), el punto y coma, la asignación, los operadores aritméticos o lógicos, etc.
tiras no específicas, como identificadores, constantes o etiquetas.
2. Prio Priori rida dadd de de los los toke tokens ns
Se da prioridad al token con el lexema más largo: Si se lee “>=” y “>” se reconoce el primero.
Si el mismo mismo lexem lexemaa se puede puede asoci asociar ar a dos tokens, tokens, estos estos patron patrones es estarán definidos en un orden determinado.
Ejemplo:
While → palabra reservada “while”
letra (letra | digito)* → identificador
Si en la entrada aparece “while”, se elegirá la palabra reservada por estar primero.
Si estas especificaciones iniciales aparecieran en orden inverso, se reconocería un token identificador.
3.7.2.PATRÓN 3.7.2.PATRÓN O EXPRESIÓN REGULAR Definen las reglas que permiten identificar los componentes léxicos o tokens. 3.7.3.LEXEMA Es cada secuencia de caracteres concreta que encaja con un patrón, es decir, decir, es como una instancia de un patrón. Ejm: 8, 23, 50 (son lexemas que encajan con el patrón ( 0 | 1 | 2 | ... | 9) + ) Una vez detectado que un grupo de caracteres coincide con un patrón, se ha detectado un lexema. A continuación continuación se le asocia un número, que se le pasará
al sintáctico, y, si es necesario, información adicional, como puede ser una entrada en la tabla de símbolos. La tabla de símbolos suelen ser listas encadenadas de registros con parte variable: listas ordenadas, árboles binarios de búsqueda, tablas hash, etc. Ejm: Hacer un analizador léxico que nos reconozca los números enteros, los números reales y los identificadores de usuario. Vamos a hacer este ejemplo en C. Terminales
Expresión Regular
( 0 ... 9) +
NUM_ENT
(0 ... 9)*. (0 ... 9) +
NUM_REAL
(a ... z) z) (a ... z 0 ... ... 9) *
ID
Asociado a la categoría gramatical de número entero tendremos el token NUM_ENT NUM_ENT que puede puede equivale equivalerr por ejemplo al número 280; 280; asociado asociado a la catego categoría ría gramat gramatica icall número número real real tendr tendremo emoss el token token NUM_RE NUM_REAL AL que equivale al número 281; asociado a la categoría gramatical identificador de usuario tendremos el token ID que equivale al número 282. ( 0 ... 9) +
{ return 280;}
(0 ... 9)*. (0 ... 9) +
{ return 281;}
(a ...z) (a ...z 0...9) *
{ return 282;}
Si tuviéramos como texto de entrada el siguiente: 95.7 99 hola El analizador léxico intenta leer el lexema más grande; el 95 encaja con el primer patrón, pero sigue, al encontrarse el punto, se da cuenta de que también encaja con el segundo patrón, entonces como este es más grande, toma la acción del segundo patrón, return NUM_REAL. El 99 coincide con el patrón NUM_ENT, y la palabra con ID. Los espacios en blanco no coinciden con ningún patrón. En vez de trabajar con los números 280, 281, 282, se definen mnemotécnicos. # define NUM_ENT # define NUM_REAL
280 281
# define NUM_ID
282
(“ ”\t \n) (0 ... 9) +
{return NUM_ENT;}
(0 ... 9) *. (0 ... 9) +
{return NUM_REAL;}
(a ... z) (a ... z 0 ... 9)*
{return ID;}
Las palabras que entran por el patrón (“ ”\t \n) no tienen acción asociada, por lo que , por defecto, se consideran solo espaciadores. Ejemplo: Programa UNO; Inicio Escribe (“Hola”); Fin. Cuya tabla de tokens es la siguiente: Programa UNO ; Inicio Escribe ( “Hola” ) ; Fin.
Palabra reservada Identificador Delimitador Palabra reservada Palabra reservada Símbolo Identificador compuesto Símbolo Delimitador Palabra reservada
3.8. DESCRIPCIÓN DE UN ANALIZADOR LÉXICO El análisis léxico es un análisis de los caracteres:
Parte de éstos y por medio de patrones reconoce los lexemas
Envía al analizador sintáctico el componente léxico y sus atributos
Puede hacer tareas adicionales: eliminar blancos, control líneas.
Gráfico Nº 10: Descripción del analizador léxico El analizador léxico y el sintáctico forman un par productor-consumidor. En algunas situaciones, el analizador léxico tiene que leer Algunos caracteres por adelantado para decidir de qué token se trata.
Gráfico Nº 11: Par Productor-Consumidor 3.9. UNIDADES UNIDADES DE LÉXICO LÉXICO Categorías en que se clasifican las cadenas de caracteres válidos en un lenguaje. Los caracteres válidos reciben el nombre de alfabeto. Por ejemplo, el alfabeto de Pascal es: A-Z, a-z, 0-9, _, =, :, ;, ,, , -, ', ", *, /, (, ), [, ], ., <, > y las unidades de léxico para pascal son:
identificadores
literales numéricas
operadores aritméticos
cadenas de caracteres
separadores
operadores relacionales relacionales
operadores lógicos
comentarios
Con respecto al lenguaje para controlar al ROBOT, tenemos que su alfabeto es: n,o,r,t,e,s, ,u,i,c y las unidades de léxico son:
órdenes
(norte, sur, este, oeste, inicio)
y espacios en blanco.
3.10.EL ROL DEL ANALIZADOR LÉXICO Aunque el analizador de léxico es la primera etapa del proceso de compilación, no es quien lo inicia. Pudiera considerarse que el analizador de léxico hace su pro proce cesa sami mien ento to y enví envíaa sus sus resu result ltad ados os al anal analiz izad ador or gram gramat atic ical al,, como como secu secuen enci cial alme ment ntee se apre apreci ciaa en el proc proces esoo de comp compil ilac ació ión; n; no es así: así: La compilación empieza con el analizador gramatical quien solicita un token para realizar su trabajo; el analizador de léxico reune símbolos y envía el token correspondiente a la unidad de léxico que conformó al analizador gramatical y espera una nueva solicitud de token. Como se aprecia en la figura siguiente, el analizador de léxico está supeditado por el analizador gramatical.
Gráfico Nº 12: Rol del analizador léxico Durante estas etapas se tiene comunicación con la tabla de símbolos que concentra información de las entidades empleadas en el programa.
3.11.TRATAMIENTO DE LOS ERRORES Un traductor debe adoptar alguna estrategia para detectar, informar y recuperarse para seguir analizando hasta el final. Las respuestas ante el error pueden ser:
Inaceptables: Provocadas por fallos del traductor, entrada en lazos infinitos, infinitos, producir resultados erróneos, erróneos, y detectar sólo el primer error y detenerse.
Aceptables: Evitar la avalancha de errores (mala recuperación) y, aunque más complejo, informar y reparar el error de forma automática. La conducta de un Analizador de Léxico es el de un Autómata finito o “scanner”.
Detección del error: El analizador de Léxico detecta un error cuando no existe transición desde el estado que se encuentra con el símbolo de la entrada. El símbolo en la entrada no es el esperado.
Los errores léxicos se detectan cuando el analizador léxico intenta reconocer componentes léxicos y la cadena de caracteres de la entrada no encaja con ningún patrón. Son situaciones en las que usa un carácter invalido (@,$,",>,...), que no perte pertenec necee al vocabu vocabula lario rio del del lengu lenguaj ajee de progra programac mación ión,, al escri escribir bir mal un identificador, palabra reservada u operador. Errores léxicos típicos son: 1. nombre ilegales ilegales de identificado identificadores: res: un nombre contiene contiene caracteres inválidos. inválidos. 2. Núme Número ross inco incorre rrect ctos os:: un nume numero ro cont contie iene ne cara caract cter eres es invá inváli lido doss o no esta esta formado correctamente, correctamente, por ejemplo 3,14 en vez de 3.14 o 0.3.14. 3. errores errores de ortografía ortografía en palabras reserva reservadas: das: caracter caracteres es omitidos, omitidos, adicion adicionale aless o cambiados de sitio, por ejemplo la palabra while en vez de while. 4. fin fin de archi archivo vo:: se detec detecta ta un fin fin de archi archivo vo a la mitad mitad de un compo compone nent ntee léxico. Los errores errores léxicos léxicos se deben deben a descui descuidos dos del programa programador dor..
En general general,, la
recuperación de errores léxicos es sencilla y siempre se traduce en la generación de un error de sintaxis que será detectado mas tarde por el analizador sintáctico cuando el analizador léxico devuelve un componente léxico que el analizador sintáctico no espera en esa posición.
Los métodos de recuperación de errores léxicos se basan bien en saltarse caracteres caracteres en la entrada hasta que un patrón se ha podido reconocer; o bien usar otros métodos más sofisticados sofisticados que incluyen la inserción, borrado, sustitución de un carácter en la entrada o intercambio de dos caracteres consecutivos. Una buena estrategia para la recuperación de errores léxicos:
si en el momento de detectar el error ya hemos pasado por algún estado final ejecutamos la acción correspondiente al ultimo estado final visitado con el lexema formado hasta que salimos de el; el resto de caracteres leídos se devuelven al flujo de entrada y se vuelve al estado inicial;
Si no hemos pasado por ningún estado final, advertimos que el carácter encontrado no se esperaba, lo eliminamos y proseguimos con el análisis.
3.12.TRATAMIENTO 3.12.TRATAMIENTO DE PALABRAS RESERVADAS RESERVADAS Son aquellas que los lenguajes de programación “reservan” para usos particulares. ¿Cómo diferenciarlas de los identificadores? identificadores?
Resolución implícita: reconocerlas todas como identificadores, utilizando una tabla adicional con las palabras reservadas que se consulta para ver si el lexema reconocido es un identificador o una palabra reservada.
Resolución explícita: se indican todas las expresiones regulares de todas las palabras reservadas y se integran los diagramas de transiciones resultantes de sus especificaciones léxicas en la máquina reconocedora.
3.13.CONSTRUCCIÓN 3.13.CONSTRUCCIÓN DE UN ANALIZADOR LÉXICO Los analizadores léxicos pueden construirse:
Usando generadores de analizadores léxicos: Es la forma más sencilla pero el código generado por el analizador léxico es más difícil de mantener y puede resultar menos eficiente.
Escribiendo el analizador léxico en un lenguaje de alto nivel: permite obtener analizadores léxicos con más esfuerzo que con el método anterior pero más eficientes y sencillos de mantener.
Escribiendo el analizador léxico en un lenguaje ensamblador: Sólo se utiliza en casos específicos debido a su alto coste y baja portabilidad.
3.14.CONCEPTO 3.14.CONCEPTO DE EXPRESIÓN REGULAR El objetivo de las expresiones regulares es representar todos los posibles lenguajes definidos sobre un alfabeto ∑, basándose en una serie de lenguajes primitivos, y unos operadores de composición. Lenguajes primitivos serian el lenguaje vació, el lenguaje formado por la palabra vacía, y los lenguajes correspondientes a los distintos símbolos del alfabeto. Los operadores de composición son la unión, la concatenación, el cierre y los paréntesis. 3.15.DEFINICIÓN 3.15.DEFINICIÓN DE EXPRESIÓN REGULAR Dado un alfabeto finito
, las expresiones regulares sobre
se definen de forma
recursiva por las siguientes reglas: 1. Las siguien siguientes tes expresi expresiones ones son son expresione expresioness regulares regulares primitiva primitivas: s:
φ
λ
α, con α ∈
2. Sean α y β expresion expresiones es regulares regulares,, entonces entonces son expresio expresiones nes regulares regulares derivadas:
α + β (union)
(concatenacion) α . β (o simplemente αβ) (concatenacion)
r epeticiónn l|A|AA|AAA..) α* (cierre) (A* repetició
(α)
3. No hay más más expre expresio siones nes reg regula ulares res sobr sobree
que las construidas mediante estas
reglas. Observación: La precedencia de los operadores es la siguiente (de mayor a menor): 1. ( ) 2. * cierre
3. . con conca cate tena naci ción ón 4. + unión Ejemplo: Algunos ejemplos de expresión regular son: (0 + 1)*01 (aa + ab + ba + bb)* a*(a + b) (aa)*(bb)*b 3.16.OPERACIONES 3.16.OPERACIONES DE EXPRESIONES REGULARES
Selección entre alternativas. la cual se indica mediante el metacaracter |
Concatenación. La concatenacion entre dos expresiones regulares R y R y S se expresa por RS. por RS.
Repetición. Se indica mediante el metacaracter *
3.17.LENGUAJE DESCRITO POR UNA EXPRESIÓN REGULAR Sea r una expresión regular sobre ∑. El lenguaje descrito por r, L(r), se define recursivamente recursivamente de la siguiente forma:
Ejemplo:
L(a*(a+b)) = L(a*)L((a+b)) = L(a)*L(a+b) = L(a)*(L(a) ∪ L(b)) = {a}*({a} ∪ {b}) = {λ, a, aa, aaa,...}{a,b} = {a, aa, ..., b, ab, aab, ...} = {a n| n ≥ 1}∪{ an b | n ≥ 0}. L((aa)*(bb)*b)= {a2n b2m+1 | n,m ≥ 0}. Si
= {a,b,c},entonces {a,b,c},entonces L(( a + b + c) )=
L(a*(b + c))
L(0*10*)
*
3.18.TEOREMAS DE EQUIVALENCIA Tal como indica su nombre, mediante expresiones regulares se pueden representar lenguajes regulares. De hecho, la clase de lenguajes que se pueden representar mediante una expresión regular, es equivalente a la clase de lenguajes regulares. Hasta ahora hemos visto que los lenguajes regulares pueden describirse mediante:
Gramáticas lineales por la izquierda,
Gramáticas lineales por la derecha,
Autómatas finitos deterministas,
Autómatas finitos no deterministas.
Por tanto, deben existir algoritmos que permitan obtener un autómata o una gramática regular a partir de una expresión regular y viceversa. 3.19.MATRICES DE TRANSICIÓN Una matriz o tabla de transiciones es un arreglo bidimensional cuyos elementos proporcionan el resumen de un diagrama de transiciones. Para elaborar una tabla de transiciones, debe colocarse cada estado del diagrama de transiciones en una fila del arreglo y cada símbolo o categoría de símbolos con posibilidades de ocurrencia en la cadena de entrada, en una columna. El elemento que se encuentra en la fila m columna n es el estado que se alcanzaría en el diagrama de transiciones al dejar al estado m a través de un arco de etiqueta n. Al no existir
algún arco que salga del estado m, entonces la casilla correspondiente de la tabla se marca como un estado de error. En la siguiente figura se presenta un ejemplo de un diagrama de transiciones que representa la sintaxis para un número de punto flotante, seguido de la tabla de transiciones correspondiente. Estado 1 2 3 4 5 6 7
Dígito 2 2 4 4 7 7 7
. Error 3 Error Error Error Error Error
E Error 5 Error 5 Error Error Error
+ Error Error Error Error 6 Error Error
Error Error Error Error 6 Error Error
FDC Error Error Error Aceptar Error Error Aceptar
Existe también La matriz de transición de estados se creó dando valores de estado a cada uno de los tipos de palabra y utilizando las reglas de la gramática respecto a la relación que existe entre cada tipo de palabra
3.20.REPRESENTACIÓN DE LOS AUTÓMATAS
Gráfico Nº 13: representación de un autómata
Acepta las secuencias: abc(dc)*
Ej. abc, abcdc, abcdcdc, abcdcdc... 3.21.AUTÓMA 3.21.AUTÓM ATA FINITO DETERMINISTA Un autómata finito determinista consiste en un dispositivo que puede estar en un estado de entre un número finito de los mismos; uno de ellos será el estado inicial y por lo menos uno será estado de aceptación. Tiene un flujo de entrada por el cual llegan los símbolos de una cadena que pertenecen a un alfabeto determinado. Se detecta el símbolo y dependiendo de este y del estado en que se encuentre hará una transición a otro estado o permanece en el mismo. El mecanismo de control (programa) es que determina cual es la transición a realizar. La palabra finito se refiere a que hay un número finito de estados. La palabra determinista es porque el mecanismo de control (programa) no debe tener ambigüedades, es decir, en cada estado solo se puede dar una y solo una (ni dos ni ninguna) transición para cada símbolo posible (en el ejemplo anterior, la tabla de transiciones era determinista en ese caso, no así el diagrama, aunque podría serlo como veremos mas tarde). El autómata acepta la cadena de entrada si la máquina cambia a un estado de aceptación después de leer el último símbolo de la cadena. Si después del último símbolo la máquina no queda en estado de aceptación, se ha rechazado la cadena. Si la máquina llega al final de su entrada antes de leer algún símbolo la entrada es una cadena vacía (cadena que no contiene símbolos) y la representaremos con Solo aceptará
.
si su estado inicial es de aceptación.
Un autómata finito determinista (AFD) consiste en una quíntupla (Q, ∑, δ, q0, F) donde:
Q es un conjunto finito de estados
∑ es el alfabeto de la máquina
δ :Q x ∑,Q (es la función total de transición) transición)
q0 ∈ Q es el estado inicial
F
Q es el conjunto de los estados de aceptación (estados finales).
Lee los caracteres de derecha a izquierda
Para cada carácter leído, si para un a ∈ ∑ y q, p ∈ S se tiene que δ(q, a) = p, significa que siempre que el automata este en el estado q y le llega el carácter a pasará al estado p Ejemplo: Automata que acepta cadenas con un número par de ceros y un numero par de unos: AFD = {S, A, B, C}, {0,1},δ, Q, {Q} M se puede definir extensivamente con:
Representación gráfica:
3.22.AUTÓMA 3.22.AUTÓM ATA FINITO NO DETERMINISTA La única diferencia con los AFD está en que en la transición en un estado determinado puede haber, para un mismo símbolo, más de un arco o no haber ninguno. Decimos que un autómata finito no determinista acepta una cadena si es posible que su análisis deje a la máquina en un estado de aceptación. Decimos si es
posible, pues si se toma el camino equivocado no se aceptaría una cadena que podría ser válida (una cadena del lenguaje aceptado por este autómata, designado por L(M). Un autómata finito no determinista (AFN o AFND) consiste en una quíntupla: (Q, ∑, δ, q0, F) donde:
Q es un conjunto finito de estados posibles del automata
∑ es el alfabeto de la máquina
δ :Q x ∑,2Q (es la función total de transición) transición)
q0 ∈ Q es el estado inicial
F
Q es el conjunto de los estados de aceptación (estados finales).
Ejemplo: AFND que reconoce en {a, b, c}* tales que el ultimp símbolo en la caden de entrada aparecía también anteriormente en la cadena. En este AFND, seria F = {q0, q1, q2, q3, q4}, {a, ,b, c}, δ, q0, { q4}
Sea la entrada aca:
Concluyendo: δ(q0, aca) = { q0, q1, q3} ∪ { q1, q4}
= { q0, q1, q3, q4} Y como δ(q0, aca) ∩ F = {q4}, la cadena se acepta.
CAPÍTULO IV: ANÁLISIS SINTÁCTICO 4.1. 4.1. GRAMÁT GRAMÁTICA ICASS
Tipo 0 o Sin Restricciones o Estructuradas por Fase (MT: Maquinas de Turing ) G = (N, T, P, S) N:
Conjunto de Símbolos No Terminales
T:
Conjunto de Símbolos Terminales
P:
Conjunto de Reglas de Producción
S∈ N: N: Símb Símbol oloo Inic Inicia iall
Tipo 1 o Sensibles al Contexto (CSG) (ALA: Autómata Linealmente Acotado ) G = (N, T, P, S) N:
Conjunto de Símbolos No Terminales
T:
Conjunto de Símbolos Terminales
P:
Conjunto de Reglas de Producción
S∈ N: N: Símb Símbol oloo Inic Inicia iall P⊆ (T∪Vn)* Vn (T∪Vn)* x (T∪Vn)* α
β
con |α| ≤ |β|
Tipo 2 o Libres de Contexto (CFG) (AP: Autómatas de Pila) G = (N, T, P, S) N:
Conjunto de Símbolos No Terminales
T:
Conjunto de Símbolos Terminales
P:
Conjunto de Reglas de Producción
S∈ N: N: Símb Símbol oloo Inic Inicia iall P⊆ N x (T∪Vn)* Ejemplo: Supóngase que utilizamos E en lugar de para la variable de la gramática. Podemos expresar esta gramática de la manera formal como:
G = (N,T,P,S) (N,T,P,S)
Donde: T = { + , * , ( , ) , id } N = { E } P={EE+E EE*E E(E) E id } S=E
Tipo 3 o Regulares (AF: Autómatas Autómatas Finitos) Definen la sintaxis de los identificadores, identificadores, números, cadenas y otros símbolos básicos del lenguaje. G = (N, T, P, S) N:
Conjunto de Símbolos No Terminales
T:
Conjunto de Símbolos Terminales
P:
Conjunto de Reglas de Producción
S∈ N: N: Símb Símbol oloo Inic Inicia iall Regular a Derec recha:
P ⊆ N x (TN ∪ T ∪{λ}) A a | aB
(lineal por la derecha)
Regu Regula larr a Izqu Izquie ierd rda: a: P ⊆ N x (NT ∪ T ∪{λ}) A a | Ba
(lineal por la izquierda)
Donde: A , B ε N , a ε T* Las gramáticas regulares guardan estrecha relación con los autómatas finitos. Las gramáticas estudiadas en la teoría de lenguajes son la 2 y 3.
4.2. GRAMÁTICAS LIBRES DE CONTEXTO CONTEXTO Y ANÁLISIS SINTÁCTICO SINTÁCTICO El análisis gramatical es determinar la sintaxis, o estructura, de un programa. Por esta razón también se le conoce como análisis sintáctico. La sintaxis de un lengu lenguaj ajee de progra programa mació ciónn por lo regula regularr se determ determina ina median mediante te las reglas reglas gramaticales gramaticales de una gramática libre de contexto. contexto. Una gramática libre de contexto util utiliz izaa conv conven enci cion ones es para para nomb nombra rarr y oper operac acio ione ness mu muyy simi simila lare ress a las las correspondientes en las expresiones regulares. Con la única diferencia de que las reglas de una gramática libre de contexto son recursivas. Las estructuras de datos utilizadas para representar la estructura sintáctica de un lenguaje ahora también deben ser recursivas en lugar de lineales. La estructura básica empleada es por lo regular alguna clase de árbol, que se conoce como árbol de análisis gramatical o árbol sintáctico. Existen dos categorías generales de algoritmos: de análisis sintáctico descendente y de análisis sintáctico ascendente (por (por la mane manera ra en que que cons constr truy uyen en el árbo árboll de anál anális isis is gram gramat atic ical al o árbo árboll sintáctico). 4.3. GRAMÁTICA GRAMÁTICAS S LIBRES DE CONTEXTO CONTEXTO Como sabemos, en una gramática libre de contexto (GLC) G = (N, T, P, S) las producciones tienen la siguiente forma:
Las Las GLCs GLCs ti tien enen en una una gran gran im impo port rtan anci ciaa en la defi defini nici ción ón de leng lengua uaje jess
de
programación, interpretación del lenguaje natural, construcción de compiladores, etc... Por comodidad a la hora de definir lenguajes, la definición anterior se extiende a la siguiente:
En este tema veremos que una GLC escrita de esta forma más general, siempre puede escribirse en la versión más restrictiva (sólo S puede producir ).
4.4. CONCEPTO CONCEPTOS S SOBRE GLCs GLCs 4.4.1.ÁRBOL DE DERIVACIÓN Sea G=(N,T,P,S) una GLC. Un árbol es un árbol derivación para G si: 1. Todo vértice tiene una etiqueta tomada de T ∪ N ∪ {λ} 2. La etiqueta de la raíz es el símbolo inicial S. 3. Los vértices interiores tienen etiquetas de N. 4. Si un nodo n tiene etiqueta A y n 1, n 2, n 3, n4,...., nk , respectivamente respectivamente
son son hijo hijoss del del vért vértic icee n orde ordena nado doss de izqu izquie ierd rdaa a dere derech cha, a, con con etiquetas x 1, x2, x3,.... xk , respectivamente, entonces:
Debe ser una producción en P 5. Si el vértice n tiene etiqueta λ, entonces n es ona hoja y es el unico
hijo de su padre. Ejemplo: Sea G = {N, T, P, S} una GLC cobn P: S ab | aSb. La derivación de la cadena aaabbb sera S ⇒ aSb ⇒ aaSbb ⇒ aaabbb y el arbol de derivación:
4.4.2.GRAMÁTICAS 4.4.2.GRAMÁTICAS NO AMBIGUAS Sea G = ( T , N , P , S ) que acepta expresiones aritméticas como: X+Y–X*Y T={X,Y,+,-,*,/,(,)} N = { EXPR , TERM , FACTOR } P = { EXPR
TERM | EXPR + TERM | EXPR – TERM
TERM
FACTOR | TERM * FACTOR | TERM / FACTOR
FACTOR X | Y | ( EXPR ) S = {EXPR} G no es ambigua, porque tiene un solo árbol de derivación
Derivación por la izquierda Se realiza el reemplazo de cada N que esta más a la izquierda EXPR EXPR – TERM
EXPR + TERM – TERM
TERM + TERM – TERM
FACTOR + TERM – TERM
X + TERM – TERM
X + FACTOR FACTOR – TERM
X + Y – TERM
X + Y – TERM * FACTOR FACTOR
X + Y – FACTOR FACTOR * FACTOR
X + Y – X * FACTOR
X +Y–X *Y
Derivación por la derecha Se realiza el reemplazo de cada N que esta más a la derecha EXPR EXPR – TERM
EXPR - TERM * FACTOR
EXPR - TERM * Y
EXPR - FACTOR * Y
EXPR - X * Y
EXPR + TERM - X * Y
EXPR + FACTOR FACTOR - X * Y
EXPR + Y - X * Y
TERM + Y - X * Y
FACTOR + Y - X * Y
X +Y–X *Y
Arbol de derivacion:
4.4.3.GRAMÁTICAS 4.4.3.GRAMÁTICAS AMBIGUAS Sea G = ( T , N , P , S ) que acepta expresiones aritméticas como: X+Y–X*Y T={X,Y,+,-,*,/,(,)} N = { EXPR , OP } P = { EXPR
OP
S = {EXPR}
EXPR OP EXPR | ( EXPR ) | X | Y + | - | * | /
G es ambigua, porque tiene más de un árbol de derivación
Derivación por la izquierda Se realiza el reemplazo de cada N que esta más a la izquierda EXPR ( EXPR )
( EXPR OP EXPR )
( X OP EXPR )
( X + EXPR )
( X + ( EXPR ) )
( X + ( EXPR OP EXPR ) )
( X + ( Y OP EXPR ) )
( X + ( Y - EXPR ) )
( X + ( Y – ( EXPR OP EXPR ) ) )
( X + ( Y – ( X OP EXPR ) ) )
( X + ( Y – ( X * EXPR ) ) )
(X+(Y–(X*Y)))
Derivación por la derecha Se realiza el reemplazo de cada N que esta más a la derecha EXPR EXPR OP EXPR
EXPR OP Y
EXPR * Y
( EXPR ) * Y
( ( EXPR ) ) * Y
( ( EXPR OP EXPR ) ) * Y
( ( EXPR OP ( EXPR ) ) ) * Y
( ( EXPR OP ( EXPR OP EXPR ) ) ) * Y
( ( EXPR OP ( EXPR OP X ) ) ) * Y
( ( EXPR OP ( EXPR - X ) ) ) * Y
( ( EXPR OP ( Y - X ) ) ) * Y
( ( EXPR + ( Y - X ) ) ) * Y
( ( X+ (Y - X ) )) *Y
4.5. GRAMÁTIC GRAMÁTICA A BNF La Notación BNF o gramática libre de contexto, es una de las gramáticas más usadas en los lenguajes de programación. BNF ( Backus Naur Form ) fue desarrollada para la definición sintáctica de Algol en 1960 por John Backus. Esta gramática maneja una sintaxis de una forma muy sencilla ya que solo se interesa por la forma como están estructuradas las distintas oraciones basándose en diversas reglas. El lenguaje a analizar se toma como una serie de cadenas de caracteres de longitud finita bajo un cierto orden. Cada cadena debe tener ciertas reglas para poder analizar su estructura. Por ejemplo la notación de una cadena de un dígito definido puede ser: < dígito > ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Aquí podemos ver que esta regla maneja las distintas alternativas que puede contener un dígito. El símbolo ' | ' significa ' o ' , ::= significa un implica, significa la lo que estamos definiendo ( identificador ) y dígito es una categoría sintác sintácti tica ca o un no termi terminal nal . Con esto podemo podemoss empez empezar ar a defini definirr alguna algunass estructuras como el IF. IF. < enunciado condicional > ::= if < expresión booleana > then < enunciado > else < enunciado > | if < espresión booleana > then < enunciado >
Una característica de todo esto es que una categoría sintáctica se puede volver a llamar así mismo para ir definiendo estructuras recursivas y/o más grandes. Por ejemplo : < entero sin signo > ::= < dígito > | < entero sin signo >< dígito > Con esto ,vemos que la notación BNF maneja una serie de reglas gramaticales que empiezan desde a lo más pequeño hasta lo más grande. Ahora bien, existen otras gramáticas que se basan en esta gramática BNF.
4.6. ÁRBOLES DE ANÁLISIS SINTÁCTICOS En esta gramática podemos usar identificadores que se llamen a sí mismos, pero en otra notación : S -> SS | ( S ) | ( ) En esta expresión se usa -> en vez de ::= , pero sin embargo significan lo mismo. El símbolo ( ) nos dice el final de este llamamiento recursivo, el cual puede formar una serie de cadenas, cadenas, donde S es cualquier cualquier expresión. expresión. Por decir :S forma a (S) y este a (SS) y este a ( ( ) S ) y este a ( ( ) ( ) ) . Ya que existe él | y las posibles terminaciones terminaciones o símbolos terminales ( S ) y ( ). Cada derivación se llama forma sentencial. Con ello aquí se puede decir que un lenguaje es un conjunto de formas senténciales, dónde cada una tiene símbolos terminales. Cada cadena tiene la posibilidad de formar estructuras más grandes y con ello se debe tener cuidado al formar las estructuras que puedan ser entendibles y no provoquen confusión para los programadores. Cada cadena es analizada por las reglas gramaticales, y el análisis provocara una salida buena o una mala, si es lo ultimo, se produce un error en el análisis sintáctico. A medida que se vayan produciendo análisis y creando estructuras más grandes y recursivas , estas ultimas van formando árboles que pueden terminar en símbolos terminales en sus ramas ( árbol de análisis sintáctico ) pero existiendo una relación entre ellas, así que cada identificador es el mismo en cada rama ( según su
estructura propia de cada uno ) . Este árbol nos muestra la mayor parte de nuestro programa.
4.7. EXTENSIÓN DE LA NOTACIÓN NOTACIÓN BNF La sencillez de la notación BNF puede llegar a complicarse, ya que cuando se van definiendo los identificadores, estos se pueden reducir a partes más pequeñas provocando problemas para definir todos los identificadores. Para ello se usa una notación un poco más fácil. 4.8. LA NOTACIÓN NOTACIÓN BNF EXTENDIDA Esta usa nuevas reglas y símbolos: 1.- [ ... ] significa cualquier elemento optativo 2.- [ , ] significa la posibilidad de escoger de una serie de alternativas solo una opción 3.- { ... }* significa poder escoger identificadores arbitrarios. Ejemplos : < entero con signo > ::= [ + | - ] < dígito > { dígito ] * < identificador > ::= < letra > { < letra > | < dígito > } * 4.9. EL PROCESO DE ANÁLISIS SINTÁCTICO SINTÁCTICO La tarea del analizador sintáctico es determinar la estructura sintáctica de un programa a partir de los tokens producidos por el analizador léxico y, ya sea de manera explícita o implícita, construir el árbol de análisis gramatical o árbol sintáctico que represente esta estructura. Dos tipos principales de análisis:
De arriba a abajo ("top-down").
De abajo a arriba ("bottom-up").
Que sea una gramática de tipo 2 (lenguaje independiente del contexto) no nos asegur aseguraa que el autóm autómat ataa a pil pilaa sea determ determini inista sta.. Estos Estos sólo sólo repres represent entan an un subconjunto de los lenguajes i.c.. A lo largo de las páginas siguientes iremos impon im ponien iendo do diver diversas sas restri restricci ccione oness a las las gramá gramátic ticas. as. Las restri restricci ccione oness de un método no tienen porqué ser las mismas que las de otro (complement ( complementariedad). ariedad).
4.10.ANÁLISIS SINTÁCTICO ASCENDENTE Analizador sintáctico ascendente: Intenta construir un árbol de análisis sintáctico, empezando desde desde la raíz y descendiendo descendiendo hacia las hojas. hojas. Lo que es lo mismo que intentar intentar obtener obtener una derivación derivación por la izquierda izquierda para una cadena cadena de entrada, entrada, comenzando desde la raíz y creando los nodos del árbol en orden previo. Parte de la cadena de entrada para construir la inversa de una derivación por la derecha. Genera el árbol de Análisis sintáctico partiendo de las hojas hasta alcanzar el axioma.
¿Cuando puede puede reducirse por una parte izquierda izquierda lo que parece ser la parte derecha de una regla?
Puede haber partes derechas comunes
Puede haber producciones ε
Tipos de gramáticas Gramáticas LR
Conjunto más amplio de gramáticas que LL(1)
Expresión más sencillas
LR(1) Analizadores LR(1)
LALR Analizadores LALR
SLR Analizadores SLR
Condiciones SLR
Se comprueban al construir el analizador Analizadores Analizadores reducción-desplazamiento reducción-desplazamiento
Analizadores por reducción – desplazamiento
Los analizadores son tabulares
Se diferencian por el algoritmo de construcción de la tabla •
Analizador LR
•
Analizador LALR
•
Analizador Analizador SLR
El algoritmo de análisis es común
Utiliza una tabla para decidir •
Un estado de pila por fila
•
Una función Acción Acción (s, a) = dj | rk
•
dj. Desplazar al siguiente token y apilar el estado j rk. Reducir por regla k.
Una función IrA(si, A) = sj
Algoritmo de análisis
4.11.ANALIZADOR SINTÁCTICO SLR
Términos comunes o Elemento. Una regla de producción que incorpora en alguna posición de su
parte derecha un punto
o Prefijo viable. La parte situada a la izquierda del punto de algún elemento
Funciones comunes o Clausura (I)
o IrA (s, A)
Colección Canónica del conjunto de elementos o Colección de todos los conjuntos de elementos o
C = { I0, I1, …}
Construcción
Autómatas reconocedores reconocedores de prefijos viables o Los estados Sj del autómata son los conjuntos Ij de C o
Las transiciones son los símbolos N u T u {$}
Construcción
Construcción de tablas SLR
Construcción de tablas SLR a partir del autómata
4.12.ANÁLISIS SINTÁCTICO DESCENDENTE Analizado Analizadorr sintáctico sintáctico descend descendente ente:: Intenta Intenta cons constru truir ir
un árbol árbol de anál análisi isiss
sintáctico, sintáctico, empezando desde desde las hojas (la cadena) cadena) y ascendiendo hacia hacia la raíz. Lo que es lo mismo que intentar obtener una reducción desde una cadena hasta llegar al axioma. El analizador sintáctico tanto ascendente como descendente puede representarse de dos formas: mediante tabla de análisis sintáctico o mediante autómata de pilas.
Partir del axioma de la gramática
Escoger reglas gramaticales gramaticales
Hacer derivaciones por la izquierda
Procesar la entrada de izquierda a derecha
Obtener el árbol de análisis sintáctico o error
4.13.ANALIZADOR CON RETROCESO
Usa retroceso para resolver r esolver la incertidumbre
Sencillo de implementar
Muy ineficiente
4.14.TÉCNICAS DE ANÁLISIS PREDICTIVO
Propósito
Crear un analizador descendente O(n)
Debe decidir qué regla aplicar según token
La gramática debe ser LL(1)
L. Análisis de izquierda a derecha
L. Derivaciones por la izquierda
1. Un token permite decidir la regla de producción
Se elimina la recursividad
4.15.CONJUNTOS 4.15.CONJUNTOS DE PREDICCIÓN
Conjuntos de predicción. Ayudan a decidir qué regla utilizar en cada paso
Construcción
Conjunto Primero PRIM (α)
Conjunto Siguiente SIG (A)
Regla
4.16.CONJUNTO 4.16.CONJUNTO PRIMERO Si α es una forma sentencial compuesta por una concatenación de símbolos PRIM (α) es el conjunto terminales (o ε) que pueden aparecer iniciando las cadenas que pueden derivar de α
Construcción
4.17.CONJUNTO 4.17.CONJUNTO SIGUIENTE Si A es un símbolo no Terminal de la gramática SIG (A) es el conjunto de terminales (y $) que pueden aparecer a continuación de A en alguna forma sentencial derivada del axioma.
4.18.FACTORIZACIÓN POR LA IZQUIERDA Se trata de rescribir las producciones de la gramática con igual comienzo para retrasar la decisión hasta haber visto lo suficiente de la entrada como para elegir la Opción correcta.
4.19.ELIMINACIÓN DE LA RECURSIVIDAD
Tipos de recursividad
Directa. Una gramática G es recursiva si tiene alguna regla de producción que sea recursiva por la izquierda
Eliminación de la recursividad
Indirecta. Si, a partir de una forma sentencial que empieza por un no terminal se puede derivar una nueva forma no sentencial sentencial donde reaparece al principio el no terminal
Eliminación de la recursividad
http://html.rincondelvago.com/lenguajes-de-programacion_historia-yevolucion.html http://www.desarrolloweb.com/articulos/2358.php http://www.monografias.com/trabajos16/lenguaje-miranda/lenguaje-miranda.shtml http://enciclopedia.us.es/index.php/Lenguaje_imperativo http://www.monografias.com/trabajos/tendprog/tendprog.shtml http://www.monografias.com/trabajos/lengprog/lengprog.shtml http://www.zonatenisatp.com/index.php http://www.zonatenisatp.com/tema1_1_01_abstraccion.php http://www.zonatenisatp.com/tema1_2_00_abstraccion_datos.php http://www.zonatenisatp.com/tema1_2_03_lenguajes_programacion.php http://ultimaorbita.com/wiki//index.php?title=Abstraccion_de_datos_y_abstraccion_ de_control._Evolucion_segun_los_paradigmas -http://yalma.fime.uanl.mx/~elisa/teaching/prog/herencia.pdf http://ib.cnea.gov.ar/CursoOO/tipos.htm http://es.wikipedia.org/wiki/Encapsulamiento_(programaci%C3%B3n_orientada_a_ objetos) http://www.cs.uu.nl/~jeroen/courses/fp-sp.pdf http://www.inf.unitru.edu.pe/~pelm/Modelos/Funcion.html http://www.dsic.upv.es/users/elp/temas/ProgFunc.html http://juanfc.lcc.uma.es/EDU/EP/trabajos/T201.Clasificaciondelostiposdelenguajes. pdf http://www.esimez.ipn.mx/acadcompu/apuntes_notas%20breves/programacion_orie ntada_objetos.pdf http://www.monografias.com/trabajos20/paradigmas-de-programacion/paradigmasde-programacion.shtml http://static.scribd.com/docs/7a6vhoquhqs22.pdf http://www.scribd.com/doc/9762/Programacion-Orientada-a-Objetos http://platea.cnice.mecd.es/~jmarti2/materiales/resumenLePr.pdf http://horustealth.tripod.com/pascal.htm http://decsai.ugr.es/~dpelta/ProgOrdenadores/tema5.pdf http://ar.geocities.com/luis_pirir/cursos/procedimiento.htm http://www.inf.udec.cl/~mvaras/estprog/cap41.html http://www.monografias.com/trabajos/objetos/objetos.shtml http://www.gnacademy.org/text/cc/Tutorial/Spanish/node5.html http://www.gnacademy.org/text/cc/Tutorial/Spanish/node6.html -http://www.desarrolloweb.com/articulos/2358.php http://www.desarrolloweb.com/articulos/2387.php http://es.wikipedia.org/wiki/Compilador http://www.investigacion.frc.utn.edu.ar/labsis/Publicaciones/InvesDes/Compiladore s/rxc.htm http://arantxa.ii.uam.es/~alfonsec/docs/compila1.htm http://www.monografias.com/trabajos11/compil/compil.shtml http://mx.geocities.com/alfonsoaraujocardenas/compiladores.html http://platon.escet.urjc.es/grupo/docencia/automatas/ http://kataix.umag.cl/~jaguila/Iec/Compiladores/Automatas/ta_cap1_2.html http://arantxa.ii.uam.es/~alfonsec/docs/compila4.htm