67
Universidad Nacional del Santa Curso: Teoría de Compiladores Docente: Ing. Mirko Manrique Ronceros ~ 1 ~ INTRODUCCION La computadora es la máquina más versátil concebida por el hombre. Aunque inicialmente es construida como un mecanismo de cálculo de prestaciones superiores a los primeros dispositivos mecánicos y electromecánicos, con el paso del tiempo se le incorporaron capacidades para la realización de operaciones lógicas y para la manipulación de datos no numéricos. Un largo proceso evolutivo ha llevado a este dispositivo calculador de la aritmética de cifras a la generación de información, teniendo como meta inmediata la manipulación del conocimiento y como objetivo futuro la generación de conciencia. Las actuales computadoras son capaces de realizar los más complejos cálculos aritméticos, lógicos y simbólicos, de emular los más elaborados mecanismos (incluyendo a otros computadores), de simular eventos naturales y de crear mundos virtuales. Las crecientes capacidades con que se presentan generación tras generación acercan a los nuevos computadores cada vez más a la realización de tareas mucho más complejas y que se antojan imposibles, como pueden ser la emulación de la mente y el pensamiento. Todo esto se sustenta, por supuesto, en la electrónica y en la capacidad de programación del computador. Sabemos que la electrónica del computador se denomina digital y que funciona con base en valores discretos. Sabemos que es a través de códigos y estrategias de representación de datos como podemos alimentar al computador con nuestras ideas y las expresiones con que identificamos a los objetos en nuestro mundo análogo y tridimensional. También sabemos que mediante reglas de operación perfectamente definidas podemos instruir al computador en la manipulación de dichos datos que llevarán a la generación de otros y a la obtención de información en la resolución de problemas. La forma de expresar dicha instrucción y la manera de llevar a cabo su traducción a lo que el computador es realmente capaz de procesar son el tema de este curso. El tema del diseño de compiladores es usualmente visto como uno de los más complejos, áridos y abstractos. Adicionalmente suele considerarse que estos temas no dejan de ser de un interés meramente académico, a menos que se trate de una enorme compañía de software dedicada a la creación de herramientas de desarrollo. Tales creencias han dado lugar a una enorme variedad de mitos, algunos de estos por ejemplo son: "Para competir con productos de calidad en un mercado tan dinámico, amplio e internacional como es el de la informática y computación, se requiere de una enorme cantidad de recursos humanos, materiales y económicos. El desarrollo de herramientas de programación como intérpretes y compiladores, ya sea para uso propio o comercialización, está reservado para las empresas que disponen de dichos recursos. La mediana y pequeña empresa no cuenta con los recursos necesarios para desarrollos internos de esta naturaleza, además de que no los necesita; si se trata de empresas dedicadas al desarrollo de programas su mercado o está únicamente en el campo de la consultoría y el desarrollo de sistemas a la medida o programación por contrato." "El conocimiento teórico asociado con el diseño y creación de compiladores y lenguajes de programación no son necesarios para un gerente de sistemas o líder de proyecto, esto es sólo para el académico, lo importante es estar al día en lo que a tendencias y productos de hardware y software se refiere."

Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 1 ~

INTRODUCCION

La computadora es la máquina más versátil concebida por el hombre. Aunque inicialmente es

construida como un mecanismo de cálculo de prestaciones superiores a los primeros dispositivos mecánicos

y electromecánicos, con el paso del tiempo se le incorporaron capacidades para la realización de operaciones

lógicas y para la manipulación de datos no numéricos. Un largo proceso evolutivo ha llevado a este

dispositivo calculador de la aritmética de cifras a la generación de información, teniendo como meta

inmediata la manipulación del conocimiento y como objetivo futuro la generación de conciencia.

Las actuales computadoras son capaces de realizar los más complejos cálculos aritméticos, lógicos y

simbólicos, de emular los más elaborados mecanismos (incluyendo a otros computadores), de simular

eventos naturales y de crear mundos virtuales. Las crecientes capacidades con que se presentan generación

tras generación acercan a los nuevos computadores cada vez más a la realización de tareas mucho más

complejas y que se antojan imposibles, como pueden ser la emulación de la mente y el pensamiento.

Todo esto se sustenta, por supuesto, en la electrónica y en la capacidad de programación del computador.

Sabemos que la electrónica del computador se denomina digital y que funciona con base en valores

discretos. Sabemos que es a través de códigos y estrategias de representación de datos como podemos

alimentar al computador con nuestras ideas y las expresiones con que identificamos a los objetos en nuestro

mundo análogo y tridimensional. También sabemos que mediante reglas de operación perfectamente

definidas podemos instruir al computador en la manipulación de dichos datos que llevarán a la generación

de otros y a la obtención de información en la resolución de problemas. La forma de expresar dicha

instrucción y la manera de llevar a cabo su traducción a lo que el computador es realmente capaz de

procesar son el tema de este curso.

El tema del diseño de compiladores es usualmente visto como uno de los más complejos, áridos y

abstractos. Adicionalmente suele considerarse que estos temas no dejan de ser de un interés meramente

académico, a menos que se trate de una enorme compañía de software dedicada a la creación de

herramientas de desarrollo. Tales creencias han dado lugar a una enorme variedad de mitos, algunos de

estos por ejemplo son:

"Para competir con productos de calidad en un mercado tan dinámico, amplio e internacional como

es el de la informática y computación, se requiere de una enorme cantidad de recursos humanos,

materiales y económicos. El desarrollo de herramientas de programación como intérpretes y

compiladores, ya sea para uso propio o comercialización, está reservado para las empresas que

disponen de dichos recursos. La mediana y pequeña empresa no cuenta con los recursos necesarios

para desarrollos internos de esta naturaleza, además de que no los necesita; si se trata de empresas

dedicadas al desarrollo de programas su mercado o está únicamente en el campo de la consultoría y

el desarrollo de sistemas a la medida o programación por contrato."

"El conocimiento teórico asociado con el diseño y creación de compiladores y lenguajes de

programación no son necesarios para un gerente de sistemas o líder de proyecto, esto es sólo para

el académico, lo importante es estar al día en lo que a tendencias y productos de hardware y

software se refiere."

Page 2: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 2 ~

"Las actividades en informática y computación en nuestro país están dedicadas al desarrollo de

software administrativo en su mayoría. Es poco probable que los profesionistas en estas áreas se

enfrenten al reto del desarrollo de un compilador o de un lenguaje de programación."

"Es más barato comprar software que desarrollarlo. Desarrollar software es lento y caro, es más

rápido comprar algo hecho."

"El diseño de compiladores y lenguajes de programación es una actividad reservada para

investigadores y catedráticos en ciencias computacionales."

"Siempre hay que usar un compilador, el código fuente queda seguro y la ejecución es más rápida.

Los intérpretes no son adecuados para el desarrollo de sistemas de información, estos son sólo un

recurso para la programación de computadoras pequeñas y para el usuario final."

"Los compiladores son hechos por nerds o gurús de la computación que dominan obscuros

lenguajes de programación, programan en lenguaje máquina y tienen un profundo conocimiento de

la arquitectura del computador."

Aunque al final del curso se darán cuenta el porqué las aseveraciones anteriores deben ser consideradas

como falsas o imprecisas.

Lenguaje Modelo CaracterísticasC++,

Fortran,COBOL,Pascal

Compilado Sintaxis específica para tipos de datos. Ideal para el desarrollo deprogramas veloces o de tamaño reducido. Permiten la explotación deinstrucciones especiales del microprocesador. Mayor seguridad para evitaralteración o robo de código fuente.

Java PseudoCompilado

Transportabilidad absoluta. Requiere de una máquina virtual para serejecutado. Mejor desempeño que un programa interpretado pero máslento que uno compilado. Lenguajes de sintaxis rigurosa.

AWK, Basic,SQL, Lisp,

Forth

Interpretado Requiere del intérprete para su ejecución. Desempeño lento. Ideal paradesarrollos rápidos (prototipos), operaciones no planeadas y programaspequeños y simples. Lenguajes de sintaxis más relajadas y mayor libertadpara la conversión de datos.

Con el paso del tiempo los diversos lenguajes de programación han madurado. Hoy en día es posible

categorizarlos por las características que han venido exhibiendo con dicha maduración. De manera que

tenemos:

1GL o lenguajes de primera generación.- Esta fue (y continua siendo) aquella a la que pertenece el

Lenguaje Máquina, el nivel en el que datos instrucciones son dados como una serie de códigos

(binarios, octales, decimales o hexadecimales).

2GL o lenguajes de segunda generación.- Todos aquellos lenguajes ensambladores.

3GL o lenguajes de tercera generación.- También conocidos como lenguajes de nivel alto.

4GL o lenguajes de cuarta generación.- Generalmente un lenguaje 4GL es un lenguaje de propósito

específico, que proveen un lenguaje muy cercano al lenguaje natural o simbólico manejado en un

ámbito específico. Muchos lenguages son llamados 4GL cuando en realidad sólo son una mezcla de

3GL y 4GL o 3GL con extensiones de de dominio especícifico. Por ejemplo, el comando list en dBASE

es un comando propio de un 4GL pero las aplicaciones programadas en dBASE son 3GL. El siguiente

Page 3: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 3 ~

ejemplo ilustra la diferencia de una sintaxis 3GL y 4GL para abrir un registro de clientes y mostrar su

contenido en pantalla.

5GL o lenguajes de quinta generación.- Estos lenguajes comienzan a ser identificados como

aquellos que hacen uso de los ambientes gráficos para llevar a cabo la programación del

computador a través de iconos o elementos gráficos similares.

COMPILADORES Y PROGRAMAS RELACIONADOS: DEFINICIONES Y CONCEPTOS

Aunque es equivocado, es común encontrar referencias en documentación de productos, publicidad

y textos (e inclusive escuchar a la gente del medio informático) utilizando los términos traductor,

compilador e intérprete de una forma libre e indistinta. Estas palabras no se utilizan para identificar de

manera genérica a un programa que nos permitiría poder programar una computadora. Debemos ser

precisos al emplear estas palabras, ya que se refieren a programas de distinta naturaleza que realizan

labores encaminadas a un objetivo específico y particular. Aunque la conducta manifestada pueda ser

similar, su comportamiento interno definitivamente es diferente.

Genéricamente hablando, en ciencias de la computación, los procesadores de lenguajes son aquellos

programas destinados a trabajar sobre una entrada que, por la forma como ha sido elaborada, pertenece a

un lenguaje en particular reconocido o aceptado por el programa en cuestión. Los procesadores de

lenguajes se clasifican como traductores o intérpretes.

TRADUCTOR

Un traductor es un programa que recibe una entrada escrita en un lenguaje (el lenguaje fuente) a una salida

perteneciente a otro lenguaje (el lenguaje objeto), conservando su significado. En términos computacionales

esto significa que tanto la entrada como la salida sean capaces de producir los mismos resultados.

INTERPRETE

Un intérprete, por otra parte, no lleva a cabo tal transformación; en su lugar obtiene los resultados conforme

va analizando la entrada.

Los intérpretes son útiles para el desarrollo de prototipos y pequeños programas para labores no previstas.

Presentan la facilidad de probar el código casi de manera inmediata, sin tener que recurrir a la declaración

previa de secciones de datos o código, y poder hallar errores de programación rápidamente. Resultan

inadecuados para el desarrollo de complejos o grandes sistemas de información por ser más lentos en su

ejecución.

Los traductores son clasificados en compiladores, ensambladores y preprocesadores.

Compiladores

Un compilador es un programa que recibe como entrada un programa escrito en un lenguaje de nivel medio

o superior (el programa fuente) y lo transforma a su equivalente en lenguaje ensamblador (el programa

objeto), e inclusive hasta lenguaje máquina (el programa ejecutable) pero sin ejecutarlo. Un compilador es

un traductor. La forma de como llevará a cabo tal traducción es el objetivo central en el diseño de un

compilador.

Page 4: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 4 ~

Un compilador es un programa muy complejo con un número de líneas de código que puede variar de

10,000 a 1,000.000. Escribir un programa de esta naturaleza o incluso comprenderlo, no es tarea fácil, y la

mayoría de los científicos y profesionales de la computación nunca escribirán un compilador completo.

Ensamblador

Un ensamblador es el programa encargado de llevar a cabo un proceso denominado de ensamble o

ensamblado. Este proceso consiste en que, a partir de un programa escrito en lenguaje ensamblador, se

produzca el correspondiente programa en lenguaje máquina (sin ejecutarlo), realizando:

La integración de los diversos módulos que conforman al programa.

La resolución de las direcciones de memoria designadas en el área de datos para el almacenamiento

de variables, constantes y estructuras complejas; así como la determinación del tamaño de éstas.

La identificación de las direcciones de memoria en la sección de código correspondientes a los

puntos de entrada en saltos condicionales e incondicionales junto con los puntos de arranque de las

subrutinas.

La resolución de los diversos llamados a los servicios o rutinas del sistema operativo, código

dinámico y bibliotecas de tiempo de ejecución.

La especificación de la cantidad de memoria destinada para las áreas de datos, código, pila y

montículo necesarios y otorgados para su ejecución.

La incorporación de datos y código necesarios para la carga del programa y su ejecución.

Precompilador

Un precompilador, también llamado preprocesador, es un programa que se ejecuta antes de invocar al

compilador. Este programa es utilizado cuando el programa fuente, escrito en el lenguaje que el compilador

es capaz de reconocer (de aquí en adelante denominado lenguaje anfitrión-- en inglés host language),

incluye estructuras, instrucciones o declaraciones escritas en otro lenguaje (el lenguaje empotrado-- en

inglés embeded language). El lenguaje empotrado es siempre un lenguaje de nivel superior o especializado

(e.g. de consulta, de cuarta generación, simulación, cálculo numérico o estadístico, etcétera). Siendo que el

único lenguaje que el compilador puede trabajar es áquel para el cual ha sido escrito, todas las instrucciones

del lenguaje empotrado deben ser traducidas a instrucciones del lenguaje anfitrión para que puedan ser

compiladas. Así pu es un precompilador también es un traductor.

Los precompiladores son una solución rápida y barata a la necesidad de llevar las instrucciones de nuevos

paradigmas de programación (e.g. los lenguajes de cuarta generación), extensiones a lenguajes ya

existentes (como el caso de C y C++) y soluciones de nivel conceptual superior (por ejemplo paquetes de

simulación o cálculo numérico) a código máquina utilizando la tecnología existente, probada, optimizada y

confiable (lo que evita el desarrollo de nuevos compiladores). Facilitan la incorporación de las nuevas

herramientas de desarrollo en sistemas ya elaborados (por ejemplo, la consulta a bases de datos

relacionales substituyendo las instrucciones de acceso a archivos por consultas en SQL).

Compilador

ProgramaFuente

ProgramaObjeto

Page 5: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 5 ~

Resulta común encontrar que el flujo de proceso en los lenguajes de cuarta generación o de propósito

especial puede resultar demasiado inflexible para su implantación en los procesos de una empresa, flujos de

negocio o interacción con otros elementos de software y hardware, de aquí que se recurra o prefiera la

creación de sistemas híbridos soportados en programas elaborados en lenguajes de tercera generación con

instrucciones empotradas de nivel superior o propósito especial.

Pseudocompilador

Un pseudocompilador es un programa que actúa como un compilador, salvo que su producto no es

ejecutable en ninguna máquina real sino en una máquina virtual. Un pseudocompilador toma de entrada un

programa escrito en un lenguaje determinado y lo transforma a una codificación especial llamada código de

byte. Este código no tendría nada de especial o diferente al código máquina de cualquier microprocesador

salvo por el hecho de ser el código máquina de un microprocesador ficticio. Tal procesador no existe, en su

lugar existe un programa que emula a dicho procesador, de aquí el nombre de máquina virtual.

La ventaja de los pseudocompiladores que permite tener tantos emuladores como microprocesadores reales

existan, pero sólo se requiere un compilador para producir código que se ejecutará en todos estos

emuladores. Este método es una de las respuestas más aceptadas para el problema del tan ansiado

lenguaje universal o código portable independiente de plataforma.

Un intérprete es un programa que ejecuta cada una de las instrucciones y declaraciones que encuentra

conforme va analizando el programa que le ha sido dado de entrada (sin producir un programa objeto o

ejecutable). La ejecución consiste en llamar a rutinas ya escritas en código máquina cuyos resultados u

operaciones están asociados de manera unívoca al significado de la instrucciones o declaraciones

identificadas.

Ligadores

Tanto los compiladores como los ensambladores a menudo dependen de un programa conocido como

ligador, el cual recopila el código que se compila o ensambla por separado en diferentes archivos objetos, a

un archivo que es directamente ejecutable. En este sentido, puede hacerse una distinción entre código

objeto (código máquina que todavía no se ha ligado) y código de máquina ejecutable. Un ligador también

conecta un programa objeto con el código de funciones de librerías estándar, así como con recursos

suministrados por el sistema operativo de la computadora, tales como asignadotes de memoria y

dispositivos de entrada y salida. Es interesante advertir que los ligadores ahora realizan la tarea que

originalmente era una de las principales actividades de un compilador (de aquí el uso de la palabra

compilador: construir mediante la recopilación o compilación de fuentes diferentes).

Cargadores

Con frecuencia un compilador, ensamblador o ligador producirá un código que todavía no está

completamente organizado y listo para ejecutarse, pero cuyas principales referencias de memoria se hacen

relativas a una localidad de arranque indeterminada que puede estas en cualquier sitio de la memoria. Se

dice que tal código es relocalizable y un cargador resolverá todas las direcciones relocalizables relativas a

una dirección base, o de inicio, dada.

Page 6: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 6 ~

El uso de un cargador hace mas flexible el código ejecutable, pero el proceso de carga con frecuencia ocurre

en segundo plano (como parte del entorno operacional) o conjuntamente con el ligado. Rara vez un

cargador es en realidad un programa por separado.

Editores

Los compiladores por lo regular aceptan programas fuente escritos utilizando cualquier editor que pueda

producir un archivo estándar, tal como un archivo ASCII. Más recientemente, los compiladores han sido

integrados junto con los editores y otros programas en un ambiente de desarrollo interactivo o IDE. En un

caso así, un editor, mientras que aún produce archivos estándar, puede ser orientado hacia el formato o

estructura del lenguaje de programación en cuestión. Tales editores se denominaban basados en estructura

y ya incluyen algunas de las operaciones de un compilador, de manera que, por ejemplo, pueda informarse

al programador de los errores a medida que el programa se vaya escribiendo en lugar de hacerlo cuando

está compilado. El compilador y sus programas acompañantes también pueden llamarse desde el editor, de

modo que el programador pueda ejecutar el programa sin tener que abandonar el editor.

Depuradores

Un depurador es un programa que puede utilizarse para determinar los errores de ejecución en un programa

compilado. A menudo está integrado en un IDE. La ejecución de un programa con un depurador se

diferencia de la ejecución directa en que el depurador se mantiene al tanto de la mayoría o la totalidad de la

información sobre el código fuente, tal como los números de línea y los nombres de las variables y

procedimientos. También puede detener la ejecución en ubicaciones previamente especificadas

denominadas puntos de ruptura, además de proporcionar información de cuáles funciones se ha invocado y

cuáles son los valores actuales de las variables. Para efectuar estas funciones el compilador debe suministrar

al depurador la información simbólica apropiada, lo cual en ocasiones puede ser difícil, en especial en un

compilador que intente optimizar el código objeto. De este modo, la depuración se convierte en una cuestión

de compilación.

Perfiladores

Es un programa que recolecta estadísticas sobre el comportamiento de un programa objeto durante la

ejecución. Las estadísticas típicas que pueden ser de interés para el programador son el número de veces

que se llama cada procedimiento y el porcentaje de tiempo de ejecución que se ocupa cada uno de ellos.

Tales estadísticas pueden ser muy útiles para ayudar al programador a mejorar la velocidad de ejecución del

programa. A veces el compilador utilizará incluso la salida del perfilador para mejorar de manera automática

el código objeto sin la intervención del programador.

PROCESO DE COMPILACION

Page 7: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 7 ~

Un compilador se compone internamente de varias etapas, o fases que realizan distintas operaciones

lógicas. Es útil pensar en estas fases como piezas separadas dentro del compilador, y pueden en realidad

escribirse como operaciones codificadas separadamente aunque en la práctica a menudo se integren juntas.

Figura 1.- Etapas del proceso de compilación.

Page 8: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 8 ~

La entrada a este proceso es por supuesto el programa fuente. Por lo general éste es un archivo que es

creado por el usuario como un texto ASCII con o sin un formato específico aunque también puede ser el

resultado de algún otro proceso. A partir de este archivo diversos pasos pueden ser llevados a cabo:

Preprocesamiento.- Un preprocesador es la estrategia generalmente adoptada como solución a

lenguajes huéspedes, extensiones, lenguajes 4GL, o lenguajes de dominio específico. El

preprocesador es un traductor encargado de transformar dichas instrucciones a instrucciones del

lenguaje anfitrión (generalmente un tradicional 3GL) sobre las cuales finalmente trabajará el

compilador. Esta etapa es definitivamente opcional.

Análisis Léxico.- En esta fase, la cadena de caracteres que conforma al programa fuente es

despojada de comentarios, espacios en blanco y otros elementos superfluos. El programa encargado

de hacer esto es conocido como un scanner, y de aquí que al proceso se le refiera comúnmente

como scanning (exploración). Durante esta fase se identifican los elementos gramaticales usados en

la creación del programa. Cada elemento identificado es substituido por un código numérico

conocido como token.

Análisis Sintáctico.- La cadena de tokens resultante es alimentada a un programa conocido como

parser. El parser es el encargado de verificar que la secuencia y disposición de los tokens

corresponda con la sintaxis del lenguaje. Este proceso de verificación sintáctica es conocido como

parsing y es completamente guiado por la gramática del lenguaje.

Análisis Semántico y Generación de Código.- Una vez que la secuencia de tokens ha sido

validada, ésta es utilizada para identificar el sentido de la acción a realizar y generar el

correspondiente código en lenguaje máquina. Algunos compiladores recurren a la creación de código

intermedio para posteriormente generar la secuencia de instrucciones máquinas necesarias,

mientras que algunos otros proceden a la generación directa del código máquina.

Optimización de Código.- Esta es otra etapa opcional. La optimización de código es una actividad

que raya en un arte dominado solamente por un experimentado programador de ensamblador y

conocedor de la arquitectura del computador. Existen algunas técnicas desarrolladas al respecto

pero nada supera a la experiencia de un hábil programador. En esta etapa, ya sea posteriormente o

trabajando al unísono con el generador de código, secuencias de instrucciones y estructuras de

datos son examinadas buscando su substitución con secuencias, instrucciones o estructuras más

cortas, rápidas o eficientes.

Ligado.- Como paso final, todas las referencias pendientes de resolver sobre rutinas, módulos,

bibliotecas y dem´s porciones de código necesarias para el funcionamiento del programa son

cubiertas en esta parte. La resolución puede consistir desde el proporcionar meramente una

dirección o llamado a una función hasta la inclusión de enormes porciones de código.

Al final, como producto de todo este proceso, lo que se obtiene es un programa escrito en código máquina

que puede ser cargado en memoria y ejecutado. El proceso seguido por un intérprete es ligeramente

diferente, ya que mientras que cubre todas las etapas de análisis no cuenta con una fase síntesis. Un

intérprete no genera código, se limita a invocar rutinas ya escritas (proceso muchas veces llamado de

interpretación). La siguiente figura ilustra esto.

Page 9: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 9 ~

Figura 2.- Etapas del proceso de interpretación

En el caso de un pseudo-compilador, cuyo caso mejor conocido es el de Java, la diferencia

consiste en el código generado. Mientras que todas las etapas de un compilador son cubiertas, el

programa ejecutable no es creado para ser ejecutado en un procesador "real" sino para uno

"hipotético" o "imaginario" y conocido generalmente como máquina virtual. La máquina virtual es

otro programa cuyo funcionamiento simula al de un procesador. Este procesador recibe de entrada

el pseudo-código creado por el compilador y procede a la ejecución de las instrucciones contenidas

en éste; puede verse que no se trata más que de un intérprete muy sencillo.

Figura 3.- Etapas del proceso de pseudo-compilación.

Page 10: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 10 ~

La siguiente figura ilustra con mayor detalle lo que pasa en cada una de las etapas del proceso de

compilación. El procesamiento de instrucciones de un lenguaje huesped (como puede ser SQL) correría a

cargo del pre-procesador, siendo transformadas instrucciones del lenguaje anfitrión. Durante la fase de

análisis léxico el scanner se encarga de identificar cada uno de los elementos usados para escribir el

programa fuente, substituyendo a cada uno de estos por un código numérico único (tokens). En este

proceso se eliminan comentarios y espacios en blanco. Los tokens son alimentados al analizador sintáctico

que valida que su disposición está acorde a las reglas del lenguaje. Validado este el analizador semántico

procede a identificar el propósito de las diversas secuencias de tokens y buscará generar representaciones

intermedias de cada acción o directamente código máquina. Este posteriormente es optimizado.

Figura 4.- Detalle del flujo de datos y acciones en el proceso de compilación.

Page 11: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 11 ~

AMBIENTES DE COMPILACIONLos compiladores a menudo producen como resultado del análisis semántico, una forma de

representación intermedia del código fuente. Hoy en día, es cada vez más común que, en

ambientes de estación de trabajo o de computador central, todos los compiladores de los distintos

lenguajes generen el mismo código intermedio, el cual después, por un generador de código, es

transformado en el código objeto.

Esto tiene una gran ventaja: si se cambia el sistema operativo o alguna otra cosa, solo hay que

reemplazar el generador de código, y no todo todos los compiladores. La generación de códigos

intermedios aumenta la transportabilidad de los compiladores, ya que no es necesario cambiar sus

partes independientes de la máquina para un nuevo hardware distinto.

CODIGO FUENTE CODIGO INTERMEDIO CODIGO HEXADECIMAL

Int suma_enteros( int i,j, suma)

{

suma=i+j;

}

*SECTION 9 Define la sección de código.

* SECTION 14 define la sección de Pila

SECTION 9

xDEF .suma_enteros

MOVE.L 4(A7), D1

MOVE.L 8(A7), D0

MOVE.L 12(A7).A0

ADD.L D1,D0

MOVE.L D0(A0)

RTS

* SECTION 14

* Asignaciones de suma_enteros

* 4(A7) .i

* 8(A7) .j

* 12(a7) .suma

00000000 222F 0004

00000004 202F 0008

00000008 206F 000C

0000000C D081

0000000E 2080

00000010 4E75

ANALISIS Y SINTESISLa compilación de un programa consiste en analizar y sintetizar dicho programa, es decir,

determinar la estructura y el significado de un código fuente y traducir ese código fuente a un

código de máquina equivalente.

Las tareas o fases principales de un compilador son:

Análisis léxico.

Análisis sintáctico.

Análisis semántico.

Generación de código.

Podemos considerar a un programa como un flujo de caracteres que sirven como entrada para el

análisis léxico. La tarea del análisis léxico consiste en reconocer los componentes léxicos dentro de

ese flujo, es decir, transformar un flujo de caracteres en un flujo de componentes léxicos (como

Page 12: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 12 ~

los textos en lenguaje natural, podemos distinguir entre palabras y componentes léxicos: el

número de palabras determina el tamaño del vocabulario del programa, mientras que el número

de componentes léxicos determina la longitud del programa.

Por ejemplo: La proposición i:= 10;

Producirá lo siguiente:

el identificador i

el símbolo de asignación :=

el número 10

el símbolo delimitador; (punto y coma)

Los identificadores o nombres reconocidos se organizan en una tabla de símbolos, que es una

estructura de datos que contiene registros con campos de atributo para cada nombre.

El contenido de la tabla de símbolos se completa con el análisis léxico y sintáctico y se usará para

el análisis semántico y la generación de código.

El siguiente paso es el análisis sintáctico. La palabra “Sintaxis” significa “estructura del orden de

las palabras en una frase”. Otro término utilizado para el análisis sintáctico es el análisis

jerárquico. La tarea del análisis sintáctico es revisar si los símbolos aparecen en el orden correcto

(es decir, revisar si el programa fuente fue diseñado de acuerdo con la sintaxis del lenguaje de

programación) y combina los símbolos del código fuente para formar unidades gramaticales.

En esta fase se detectan errores de sintaxis como:

h + x := x * y

En general, las unidades gramaticales se organizan y representan con árboles de análisis

sintáctico o árboles sintácticos.

En la siguiente figura se muestra el árbol de análisis sintáctico de la siguiente proposición:

h:= x + y – x * y

Asignación

identificador expresión

identificador expresión

identificador expresión

identificador expresión

h := x + y – x * y

Page 13: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 13 ~

Después del análisis semantico y el de tipos. El análisis semantico es mucho más difícil que el

sintáctico, pues hay que considerar el significado de una unidad gramatical; es decir hay que

interpretarla. Esto se puede lograr traduciendo la entrada a una forma de representación

intermedia. Por ejemplo, nunca hubiéramos definido la variable h de la figura, la proposición de

asignación no tendría sentido. En forma análoga, la asignación de una variable booleana a una

variable real tampoco tendrá sentido. Este tipo de inconsistencias será reconocido por el análisis

de tipos.

El código objeto se genera en la última fase de la compilación: el generador de código. En esta

fase el código intermedio se transforma en código de maquina y la memoria necesaria quedara

determinada. Obviamente, esta es la única fase que depende del hardware, ya que por lo general,

los conjuntos de instrucciones varían de un computador a otro.

AmbigüedadSe ha de tener cuidado al considerar la estructura de una cadena según una gramática. Aunque

es evidente que cada árbol de análisis sintáctico deriva exactamente la cadena que leer en sus

hojas, una gramática puede tener más de un árbol de análisis sintáctico que genere una cadena

dada de componentes léxicos. Esta clase de gramática se dice que es ambigua. Para demostrar

que una gramática es ambigua, lo único que se requiere es encontrar una cadena de componentes

léxicos que tenga más de un árbol de análisis sintáctico. Como una cadena que cuenta con más de

un árbol de análisis sintáctico suele tener más de un significado, para aplicaciones de compilación

es necesario diseñar gramáticas no ambiguas o utilizar gramáticas ambiguas con reglas adicionales

para resolver las ambigüedades.

Por ejemplo, si se tiene la expresión 9 – 5 + 2 tiene ahora más de un árbol de análisis sintáctico.

Los dos árboles de 9 – 5 + 2 corresponden a dos formas de agrupamientos entre paréntesis de la

expresión : (9 – 5) + 2 y 9 – (5 + 2). Esta segunda forma de agrupamiento entre paréntesis da a

la expresión el valor de 2, en lugar del valor acostumbrado 6.

Asociatividad de operadoresPor convención, 9 + 5 + 2 es equivalente a (9 + 5) + 2, y 9 – 5 – 2 es equivalente a (9 – 5) – 2.

Cuando un operando con 5 tiene operadores a su izquierda y derecha, se necesitan convenciones

para decidir que operador considera es operando. Se dice que el operador + asocia a la izquierda,

porque un operando que tenga un signo + a ambos lados es tomado por el operador que esté a

su izquierda. En la mayoría de los lenguajes de programación, los cuatro operadores matemáticos,

adición, sustracción, multiplicación y división son asociativos a la izquierda.

Page 14: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 14 ~

Algunos operadores comunes, como la exponenciacion son asociativos por la derecha. Otro

ejemplo análogo, el operador de asignación = en C es asociativo por la derecha; en C la expresión

a = b = c; con un operador asociativo por la derecha, son generadas por la siguiente gramática:

derecha letra = derecha | letra

letra a | b | . . . | z

El contraste entre un árbol de análisis sintáctico para un operador asociativo por la izquierda como

– y un árbol de análisis sintáctico para un operador asociativo por la derecha como =, se muestra

la siguiente figura:

expresión derecha

expresión – digito letra = derecha

expresión – digito 2 a letra = derecha

digito 5 b letra

9 c

Procedencia de operadoresConsidere la expresión 9 + 5 * 2. Hay dos interpretaciones posibles de esta expresión: (9 + 5) * 2

o 9 + (5 * 2). La asociatividad de + y * no resuelve esta ambigüedad. Por esta razón, se necesita

conocer la precedencia relativa de los operadores cuando esté presente más de una clase de

operadores.

Se dice que * tiene mayor precedencia que + si * considera sus operandos antes que lo haga +.

En aritmética elemental, la multiplicación y división tiene mayor precedencia que la adición y

sustracción. Por tanto 5, es considerado por * en 9 + 5 * 2 y en 9 * 5 + 2; es decir las

expresiones son equivalentes a 9 + (5 * 2) y (9 * 5) + 2, respectivamente.

TRADUCCION DIRIGIDA POR LA SINTAXISPara traducir una construcción de un lenguaje de programación, un compilador puede necesitar

tener en cuenta muchas características, además del código generado para la construcción. Por

ejemplo, puede ocurrir que el compilador necesite conocer el tipo de la construcción, la posición

de la primera instrucción del código objeto o el numero de instrucciones generadas. Por tanto, los

atributos asociados con las construcciones se mencionan de manera abstracta. Un atributo puede

representar cualquier cantidad, por ejemplo, una expresión, una cadena, una posición de memoria

o cualquier otra cosa.

Page 15: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 15 ~

Notación Posfija

La notación posfija de una expresión E se puede definir de manera inductiva como sigue:

Si E es una variable o una constante, entonces la notación posfija de E es también E.

Si E es una expresión de la forma E1 op E2, donde op es cualquier operador binario,

entonces la notación posfija de E es E’1E’2op, donde E’1 y E’2 son las notación posfijas de

E1 y E2 respectivamente.

Si E es una expresión de la forma (E1), entonces la notación posfija de E1 es también la

notación posfija de E.

La notación posfija no necesita paréntesis, porque la posición y la ariedad (numero de

argumentos) de los operadores permiten solo una descodificación de una expresión posfija. Por

ejemplo, la notación posfija de (9 – 5) + 2 es 95-2+ y la notación posfija de 9-(5+2) es 952+-.

Page 16: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 16 ~

Page 17: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 17 ~

EXPRESIONES REGULARES Y AUTOMATAS

La fase de rastreo, o análisis léxico, de un compilador tiene la tarea de leer el

programa fuente como un archivo de caracteres y dividirlo en tokens. Los tokens son

como las palabras de un lenguaje natural: cada token es una secuencia de caracteres

que representa una unidad de información en el programa fuente. Ejemplos típicos de

token son las palabras reservadas, como if y while, las cuales son cadenas fijas de

letras; los identificadores, que son cadenas definidas por el usuario, compuestas por

lo regular de letras y números, y que comienzan con una letra; los símbolos

especiales, como los símbolos aritméticos + y *; además de algunos símbolos

compuestos de múltiples caracteres, tales como > = y <>. En cada caso un token

representa cierto patrón de caracteres que el analizador léxico reconoce, o ajusta

desde el inicio de los caracteres de entrada restantes.

Como la tarea que realiza el analizador léxico es un caso especial de coincidencia

de patrones, necesitamos estudiar métodos de especificación y reconocimiento de

patrones en la medida en que se aplican al proceso de análisis léxico. Estos métodos

son principalmente los de las expresiones regulares y los autómatas finitos. Sin

embargo, un analizador léxico también es la parte del compilador que maneja la

entrada del código fuente, y puesto que esta entrada a menudo involucra un

importante gasto de tiempo, el analizador léxico debe funcionar de manera tan

eficiente como sea posible. Por lo tanto, también necesitamos poner mucha atención a

los detalles prácticos de la estructura del analizador léxico.

Dividiremos el estudio de las cuestiones del analizador léxico como sigue. En primer

lugar, daremos una perspectiva general de la función de un analizador léxico y de las

estructuras y conceptos involucrados. Enseguida, estudiaremos las expresiones

regulares y por último el estudio de las máquinas de estados finitos o autómatas

finitos.

EL PROCESO DEL ANÁLISIS LÉXICO

El trabajo del analizador léxico es leer los caracteres del código fuente y formarlos en

unidades lógicas para que lo aborden las partes siguientes del compilador

(generalmente el analizador sintáctico). Las unidades lógicas que genera el

analizador léxico se denominan tokens, y formar caracteres en tokens es muy

parecido a formar palabras a partir de caracteres en una oración en un lenguaje

Page 18: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 18 ~

natural como el inglés o cualquier otro y decidir lo que cada palabra significa. En esto

se asemeja a la tarea del deletreo.

Los tokens son entidades lógicas que por lo regular se definen como un tipo

enumerado. Por ejemplo, pueden definirse en C como:

typedef enum

{IF,THEN,ELSE,PLUS,MINUS,NUM,ID,...}

TokenType;

Los tokens caen en diversas categorías, una de ellas la constituyen las palabras

reservadas, como IF y THEN, que representan las cadenas de caracteres "if' y

"then". Una segunda categoría es la de los símbolos especiales, como los símbolos

aritméticos MÁS y MENOS, los que se representan con los caracteres "+" y "—".

Finalmente, existen tokens que pueden representar cadenas de múltiples caracteres.

Ejemplos de esto son NUM e ID, los cuales representan números e identificadores.

Los tokens como entidades lógicas se deben distinguir claramente de las cadenas

de caracteres que representan. Por ejemplo, el token de la palabra reservada IF se

debe distinguir de la cadena de caracteres "if' que representa. Para hacer clara la

distinción, la cadena de caracteres representada por un token se denomina en

ocasiones su valor de cadena o su lexema. Algunos tokens tienen sólo un lexema:

las palabras reservadas tienen esta propiedad. No obstante, un token puede

representar un número infinito de lexemas. Los identificadores, por ejemplo, están

todos representados por el token simple ID, pero tienen muchos valores de cadena

diferentes que representan sus nombres individuales. Estos nombres no se pueden

pasar por alto, porque un compilador debe estar al tanto de ellos en una tabla de

símbolos. Por consiguiente, un rastreador o analizador léxico también debe construir

los valores de cadena de por lo menos algunos de los tokens.

EXPRESIONES REGULARES

Las expresiones regulares representan patrones de cadenas de caracteres. Una expresión

regular r se encuentra completamente definida mediante el conjunto de cadenas con las

que concuerda. Este conjunto se denomina lenguaje generado por la expresión

regular y se escribe como L(r), Aquí la palabra lenguaje se utiliza sólo para definir

"conjunto de cadenas" y no tiene (por lo menos en esta etapa) una relación específica con

un lenguaje de programación. Este lenguaje depende, en primer lugar, del conjunto de

caracteres que se encuentra disponible. En general, estaremos hablando del conjunto de

caracteres ASCII o de algún subconjunto del mismo. En ocasiones el conjunto será más

general que el conjunto de caracteres ASCII, en cuyo caso los elementos del conjunto se

Page 19: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 19 ~

describirán como símbolos. Este conjunto de símbolos legales se conoce como alfabeto

y por lo general se representa mediante el símbolo griego Σ (sigma).

Una expresión regular r también contendrá caracteres del alfabeto, pero esos

caracteres tendrán un significado diferente: en una expresión regular todos los símbolos

indican patrones. En este capítulo distinguiremos el uso de un carácter como patrón

escribiendo todo los patrones en negritas. De este modo, a es el carácter a usado como

patrón.

Por último, una expresión regular r puede contener caracteres que tengan

significados especiales. Este tipo de caracteres se llaman metacaracteres o

metasímbolos, y por lo general no pueden ser caracteres legales en el alfabeto,

porque no podríamos distinguir su uso como metacaracteres de su uso como

miembros del alfabeto. Sin embargo, a menudo no es posible requerir tal exclusión,

por lo que se debe utilizar una convención para diferenciar los dos usos posibles de un

metacaracter. En muchas situaciones esto se realiza mediante el uso de un carácter

de escape que "desactiva" el significado especial de un metacaracter. Unos

caracteres de escape comunes son la diagonal inversa y las comillas. Advierta que los

caracteres de escape, si también son caracteres legales en el alfabeto, son por sí

mismos metacaracteres.

Page 20: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 20 ~

Definición de expresiones regulares

Expresiones regulares básicas: Estas son precisamente los caracteres simples del

alfabeto, los cuales se corresponden a sí mismos. Dado cualquier carácter a del

alfabeto Σ, indicamos que la expresión regular a corresponde al carácter a escribiendo

L(a) = {a}. Existen otros dos símbolos que necesitaremos en situaciones especiales.

Necesitamos poder indicar una concordancia con la cadena vacía, es decir, la cadena

que no contiene ningún carácter. Utilizaremos el símbolo ε (épsilon) para denotar la

cadena vacía, y definiremos el metasímbolo ε (e en negritas) estableciendo que L(ε) =

{ε}. También necesitaremos ocasionalmente ser capaces de describir un símbolo que

corresponda a la ausencia de cadenas, es decir, cuyo lenguaje sea el conjunto vacío,

el cual escribiremos como { }. Emplearemos para esto el símbolo φ y escribiremos

L(φ) = { }. Observe la diferencia entre { } y {ε}: el conjunto { } no contiene ninguna

cadena, mientras que el conjunto {ε} contiene la cadena simple que no se compone

de ningún carácter.

Operaciones de expresiones regulares: Existen tres operaciones básicas en las

expresiones regulares: 1) selección entre alternativas, la cual se indica mediante el

metacaracter | (barra vertical); 2) concatenación, que se indica medíante

yuxtaposición (sin un metacaracter), y 3) repetición o "cerradura", la cual se indica

mediante el metacaracter *. Analizaremos cada una por turno, proporcionando la

construcción del conjunto correspondiente para los lenguajes de cadenas

concordantes.

Selección entre alternativas: Si r y s son expresiones regulares, entonces r|s es una

expresión regular que define cualquier cadena que concuerda con r o con s. En

términos de lenguajes, el lenguaje de r | s es la unión de los lenguajes de r y s, o L(r

| s) = L(r) u L(s). Como un ejemplo simple, considere la expresión regular a | b: ésta

corresponde tanto al carácter a como al carácter b, es decir, L(a | b) = L(a) U L(b) =

{a} u {b} = {a, b}. Como segundo ejemplo, la expresión regular a | ε corresponde

tanto al carácter simple a como a la cadena vacía (que no está compuesta por ningún

carácter). En otras palabras, L(a | ε) = {a, ε}.

La selección se puede extender a más de una alternativa, de manera que, por

ejemplo, L(a | b | c | d) = {a, b, c, d}. En ocasiones también escribiremos largas secuencias

de selecciones con puntos, como en a | b | ... | z, que corresponde a cualquiera de las

letras minúsculas de la a a la z.

Concatenación: La concatenación de dos expresiones regulares r y s se escribe como rs, y

corresponde a cualquier cadena que sea la concatenación de dos cadenas, con la primera

Page 21: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 21 ~

de ellas correspondiendo a r y la segunda correspondiendo a s. Por ejemplo, la expresión

regular ab corresponde sólo a la cadena ab, mientras que la expresión regular (a | b) c

corresponde a las cadenas ac y bc. (El uso de los paréntesis como metacaracteres en

esta expresión regular se explicará en breve).

Podemos describir el efecto de la concatenación en términos de lenguajes generados

al definir la concatenación de dos conjuntos de cadenas. Dados dos conjuntos de

cadenas S1 y S2, el conjunto concatenado de cadenas S1S2 es el conjunto de cadenas

de S1 complementado con todas las cadenas de S2. Por ejemplo, si S1 = {aa, b} y S2 =

{a, bb}, entonces S1S2 = {aaa, aabb, ba, bbb}. Ahora la operación de concatenación para

expresiones regulares se puede definir como sigue: L(rs)=L(r)L(s). De esta manera

(utilizando nuestro ejemplo anterior), L{(a | b) c) = L(a | b)L(c) = {a, b}{c) = {ac,

bc}.

La concatenación también se puede extender a más de dos expresiones regulares: L(r¡

r2 . . . r„) = L(ri)L(r2) . . . L(rn) = el conjunto de cadenas formado al concatenar todas las

cadenas de cada una de las L(r1), . . . , L(rn).

Repetición: La operación de repetición de una expresión regular, denominada también en

ocasiones cerradura (de Kleene), se escribe r*, donde r es una expresión regular. La

expresión regular r* corresponde a cualquier concatenación finita de cadenas, cada una

de las cuales corresponde a r. Por ejemplo, a* corresponde a las cadenas e, a, aa, aaa,

.... (Concuerda con e porque e es la concatenación de ninguna cadena concordante con

a.) Podemos definir la operación de repetición en términos de lenguajes generados

definiendo, a su vez, una operación similar * para conjuntos de cadenas. Dado un

conjunto S de cadenas, sea:

S* = {e} uSuSSuSSSu...

Ahora podemos definir la operación de repetición para expresiones regulares como

sigue:

L{r*) = L(r)*

Considere como ejemplo la expresión regular (a | bb) *. (De nueva cuenta, la razón

de tener paréntesis como metacaracteres se explicará más adelante.) Esta expresión

regular corresponde a cualquiera de las cadenas siguientes: e, a, bb, aa, abb, bba,

bbbb, aaa, aabb y así sucesivamente. En términos de lenguajes, L( (a | bb) *) = L(a |

bb)* = [a, bb}* = {ε, a, bb, aa, abb, bba, bbbb, aaa, aabb, abba, abbbb, bbaa, . . .}.

Page 22: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 22 ~

Precedencia de operaciones y el uso de los paréntesis La descripción precedente

no toma en cuenta la cuestión de la precedencia de las operaciones de elección,

concatenación y repetición. Por ejemplo, dada la expresión regular a | b*,

¿deberíamos interpretar esto como (a | b) * o como a|(b*) ? (Existe una diferencia

importante, puesto que L( (a |b) *) = {ε, a, b, aa, ab, ba, bb, .. .}, mientras que L(a

| (b*)) = {ε, a, b, bb, bbb, . . .}.) La convención estándar es que la repetición debería

tener mayor precedencia, por lo tanto, la segunda interpretación es la correcta. En

realidad, entre las tres operaciones, se le da al * la precedencia más alta, a la

concatenación se le da la precedencia que sigue y a la | se le otorga la precedencia

más baja. De este modo, por ejemplo, a | bc* se interpreta como a|(b(c*)),

mientras que ab | c*d se interpreta como (ab) | ((c*)d).

Cuando deseemos indicar una precedencia diferente, debemos usar paréntesis para

hacerlo. Ésta es la razón por la que tuvimos que escribir (a|b)c para indicar que la

operación de elección debería tener mayor precedencia que la concatenación, ya que

de otro modo a | bc se interpretaría como si correspondiera tanto a a como a bc. De

manera similar, (a l bb) * se interpretaría sin los paréntesis como a | bb*, lo que

corresponde a a, b, bb, bbb, .... Los paréntesis aquí se usan igual que en aritmética,

donde (3 + 4) * 5 = 35, pero 3 + 4 * 5 = 23, ya que se supone que * tiene

precedencia más alta que +.

Nombres para expresiones regulares A menudo es útil como una forma de simplificar

la notación proporcionar un nombre para una expresión regular larga, de modo que no

tengamos que escribir la expresión misma cada vez que deseemos utilizarla. Por

ejemplo, si deseáramos desarrollar una expresión regular para una secuencia de uno

o más dígitos numéricos, entonces escribiríamos

(0|1|2| ... |9)(0|1|2| ... |9)* o podríamos escribir

dígito dígito* donde

dígito = 0 I 1 I 2 I . . . I 9

es una definición regular del nombre dígito.

El uso de una definición regular es muy conveniente, pero introduce la complicación

agregada de que el nombre mismo se convierta en un metasímbolo y se deba

encontrar un significado para distinguirlo de la concatenación de sus caracteres. En

nuestro caso hicimos esa distinción al utilizar letra cursiva para el nombre. Advierta

que no se debe emplear el nombre del término en su propia definición (es decir, de

manera recursiva): debemos poder eliminar nombres reemplazándolos sucesivamente

con las expresiones regulares para las que se establecieron.

Page 23: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 23 ~

Antes de considerar una serie de ejemplos para elaborar nuestra definición de

expresiones regulares, reuniremos todas las piezas de la definición de una expresión

regular.

Una expresión regular es una de las siguientes:

1. Una expresión regular básica constituida por un solo carácter a, donde a proviene de

un alfabeto Σ de caracteres legales; el metacarácter ε ; o el metacarácter ε. En el pri-

mer caso, L(a) = {a}; en el segundo, L(ε) = {ε}; en el tercero, L(φ) = {}.

2. Una expresión de la forma r | s, donde r y s son expresiones regulares. En este caso,

L(r | s) = L(r) u L(s).

3. Una expresión de la forma rs, donde r y s son expresiones regulares. En este caso,

L(rs) = L(r)L(s).

4. Una expresión de la forma r*, donde r es una expresión regular. En este caso,

L(r*)=L(r)*.

5. Una expresión de la forma (r), donde r es una expresión regular. En este caso,

L((r)) = L(r). De este modo, los paréntesis no cambian el lenguaje, sólo se utilizan

para ajustar la precedencia de las operaciones.

Ejemplo1:

Consideremos el alfabeto simple constituido por sólo tres caracteres alfabéticos: Σ= {a,

b,c). También el conjunto de todas las cadenas en este alfabeto que contengan

exactamente una b. Este conjunto es generado por la expresión regular

(alc)*b(alc)*

Advierta que, aunque b aparece en el centro de la expresión regular, la letra b no

necesita estar en el centro de la cadena que se desea definir. En realidad, la repetición de

a o c antes y después de la b puede presentarse en diferentes números de veces. Por

consiguiente, todas las cadenas siguientes están generadas mediante la expresión regular

anterior: b, abc, abaca, baaaac, ccbaca, ccccccb.

Ejemplo2:

Con el mismo alfabeto que antes, considere el conjunto de todas las cadenas que

contienen como máximo una b. Una expresión regular para este conjunto se puede

obtener utilizando la solución al ejemplo anterior como una alternativa (definiendo

aquellas cadenas con exactamente una b) y la expresión regular (a l c )* como la otra

alternativa (definiendo los casos sin b en todo). De este modo, tenemos la solución

siguiente:

(alc)*|(a lc)*b(a|c)*

Page 24: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 24 ~

Una solución alternativa sería permitir que b o la cadena vacía apareciera entre las

dos repeticiones de a o c:

(a|c)*(blε)(alc)*

Ejemplo3:

Consideremos el conjunto de cadenas S sobre el alfabeto Σ = {a,b} compuesto de

una b simple rodeada por el mismo número de a:

S = {b, aba, aabaa, aaabaaa, . . .} = {anba

n|n ≠ 0}

Page 25: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 25 ~

AUTÓMATAS FINITOS

Los autómatas finitos, o máquinas de estados finitos, son una manera matemática para

describir clases particulares de algoritmos (o "máquinas"). En particular, los autómatas

finitos se pueden utilizar para describir el proceso de reconocimiento de patrones en

cadenas de entrada, y de este modo se pueden utilizar para construir analizadores

léxicos. Por supuesto, también existe una fuerte relación entre los autómatas finitos y las

expresiones regulares, y veremos en la sección siguiente cómo construir un autómata

finito a partir de una expresión regular. Sin embargo, antes de comenzar nuestro estudio

de los autómatas finitos de manera apropiada, consideraremos un ejemplo explicativo.

El patrón para identificadores como se define comúnmente en los lenguajes de progra-

mación está dado por la siguiente definición regular (supondremos que letra y dígito

ya se definieron):

identificador = letra(letra|dígito)*

Esto representa una cadena que comienza con una letra y continúa con cualquier

secuencia de letras y/o dígitos. El proceso de reconocer una cadena así se puede describir

mediar diagrama de la figura:

En este diagrama los círculos numerados 1 y 2 representan estados, que son localidades

en proceso de reconocimiento que registran cuánto del patrón ya se ha visto. Las líneas

flechas representan transiciones que registran un cambio de un estado a otro en una

coincidencia del carácter o caracteres mediante los cuales son etiquetados. En el

diagrama muestra, el estado 1 es el estado de inicio, o el estado en el que comienza el

proceso de reconocimiento. Por convención, el estado de inicio se indica dibujando una

línea con flecha sin etiqueta que proviene de "de ninguna parte". El estado 2 representa

el punto en el cual se ha igualado una sola letra (lo que se indica mediante la transición

del estado 1 al estado 2 etiquetada con letra). Una vez en el estado 2, cualquier

número de letras y/o dígitos se puede ver, y una coincidencia de éstos nos regresa al

Page 26: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 26 ~

estado 2. Los estados que representan el fin del proceso de reconocimiento, en los cuales

podemos declarar un éxito, se denominan estados de aceptación, y se indican

dibujando un borde con línea doble alrededor del estado en el diagrama. Puede haber

más de uno de éstos. En el diagrama de muestra el estado 2 es un estado de aceptación,

lo cual indica que, después que cede una letra, cualquier secuencia de letras y dígitos

subsiguiente (incluyendo la ausencia de todas) representa identificador legal.

El proceso de reconocimiento de una cadena de caracteres real como un identificador

ahora se puede indicar al enumerar la secuencia de estados y transiciones en el diagrama

q se utiliza en el proceso de reconocimiento. Por ejemplo, el proceso de reconocer

xtemp como un identificador se puede indicar como sigue:

Un DFA (por las siglas del concepto autómata finito determinístico en inglés) M se

compone de un alfabeto Σ, un conjunto de estados S, una función de transición T: S X

Σ —> S, un estado de inicio s0 ε S y un conjunto de estados de aceptación A C S. El

lenguaje aceptado por M, escrito como L(M), se define como el conjunto de cadenas

de caracteres C1C2.. .c„ con cada c¡ Σ, tal que existen estados Sj = T(s0, C1), s2 =

T(s1, c2),... ,sn = T(Sn-1, cn), con sn como un elemento de A (es decir, un estado de

aceptación).

Hacemos las anotaciones siguientes respecto a esta definición. S X Σ se refiere al

producto cartesiano o producto cruz de S y Σ: el conjunto de pares (s, c), donde

sS y cΣ La función T registra las transiciones: T(s, c) = s' si existe una transición

del estado S al estado s' etiquetado mediante c. El segmento correspondiente del

diagrama para M tendrá el aspecto siguiente:

La aceptación como la existencia de una secuencia de estados s1 = T(so, c1), s2 =

T(s1, c2),. . . , = T(sn-1, c„), con sn siendo un estado de aceptación, significa

entonces lo mismo que el

Page 27: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 27 ~

Advertimos

un número de diferencias entre la definición de un DFA y el diagrama del ejemplo

identificador. En primer lugar, utilizamos los números para los estados en el diagrama

del identificador, mientras la definición no restrinja el conjunto de estados a números.

En realidad, podemos emplear cualquier sistema de identificación que queramos para

los estados, incluyendo nombres. Por ejemplo, podemos escribir un diagrama

equivalente al de la figura como:

donde ahora denominamos a los estados inicio (porque es el estado de inicio) y

entrada_id (porque vimos una letra y estará reconociendo un identificador después de

letras y números subsiguientes cualesquiera). El conjunto de estados para este

diagrama se convierte ahora en {inicio, entrada_jd} en lugar de {1, 2}.

Una segunda diferencia entre el diagrama y la definición es que no etiquetamos las

transiciones con caracteres sino con nombres que representan un conjunto de

caracteres.

El conjunto de cadenas que contienen exactamente una b es aceptado por el siguiente

DFA:

El conjunto de cadenas que contienen como máximo una b es aceptado por el

siguiente DFA:

Page 28: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 28 ~

Definiciones regulares para constantes numéricas en notación científica como se

muestra a continuación

Un NFA (por las siglas del término autómata finito no determinístico en inglés) M

consta de un alfabeto Σ, un conjunto de estados S y una función de transición T:S X(Σ

u {ε}) -> φ(S), así como de un estado de inicio s0 de S y un conjunto de estados de

aceptación A de S. El lenguaje aceptado por M, escrito como L(M), se define como el

conjunto de cadenas de caracteres c1c2. . .cn con cada c¡ de Σ u {ε} tal que existen

estados s1 en T(s0, c1), s2 en T(s1, c2),. .., sn en T(sn-1 cn), con Sn como un

elemento de A.

Considere el siguiente diagrama de un NFA

La cadena abb puede ser aceptada por cualquiera de las siguientes secuencias de

transacciones:

Page 29: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 29 ~

En realidad las transiciones del estado 1 al estado 2 en a, y del estado 2 al estado 4 en b,

permiten que la máquina acepte la cadena ab, y entonces, utilizando la transición ε del

estado 4 al estado 2, todas las cadenas igualan la expresión regular ab+. De manera

similar, las transiciones del estado 1 al estado 3 en a, y del estado 3 al estado 4 en ε,

permiten la aceptación de todas las cadenas que coinciden con ab*. Finalmente,

siguiendo la transición e desde el estado 1 hasta el estado 4 se permite la aceptación de

todas las cadenas coincidentes con b*. De este modo, este NFA acepta el mismo

lenguaje que la expresión regular ab+ l ab* I b*. Una expresión regular más simple

que genera el mismo lenguaje es (a|ε)b*. El siguiente DFA también acepta este

lenguaje:

Considere el siguiente NFA

Page 30: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 30 ~

Este acepta la cadena acab al efectuar las transiciones siguientes:

Page 31: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 31 ~

LENGUAJES FORMALES

En matemáticas, lógica, y ciencias de la computación, un lenguaje formal es un

conjunto de palabras (cadenas de caracteres) de longitud finita en los casos más

simples o expresiones válidas (formuladas por palabras) formadas a partir de un

alfabeto (conjunto de caracteres) finito. El nombre lenguaje se justifica porque las

estructuras que con este se forman tienen reglas de buena formación (gramática)

e interpretación semántica (significado) en una forma muy similar a los lenguajes

hablados.

Un posible alfabeto sería, digamos, , y una cadena cualquiera sobre este

alfabeto sería, por ejemplo, . Un lenguaje sobre este alfabeto, que incluyera

esta cadena, sería: el conjunto de todas las cadenas que contienen el mismo número

de símbolos que , por ejemplo.

La palabra vacía (esto es, la cadena de longitud cero) se permite en este tipo de

lenguajes, notándose frecuentemente mediante , ó . A diferencia de que ocurre

con el alfabeto (que es un conjunto finito) y con cada palabra (que tiene una longitud

también finita), un lenguaje puede estar compuesto por un número infinito de

palabras.

Ejemplos de lenguajes formales:

El conjunto de todas las palabras sobre .

El conjunto es un número primo.

El conjunto de todos los programas sintácticamente válidos en un determinado

lenguaje de programación.

El conjunto de sentencias bien formadas en lógica de predicados.

Especificación de lenguajes formales

Los lenguajes formales se pueden especificar de una amplia variedad de formas, como

por ejemplo:

Cadenas producidas por una gramática formal (véase Jerarquía de Chomsky).

Cadenas producidas por una expresión regular.

Cadenas aceptadas por un autómata, tal como una máquina de Turing.

Page 32: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 32 ~

Operaciones

Se pueden utilizar varias operaciones para producir nuevos lenguajes a partir de otros

dados. Supóngase que L1 y L2 son lenguajes sobre un alfabeto común. Entonces:

La concatenación L1L2 consiste de todas aquellas palabras de la forma vw donde

v es una palabra de L1 y w es una palabra de L2

La intersección L1&L2 consiste en todas aquellas palabras que están contenidas

tanto en L1 como en L2

La unión L1|L2 consiste en todas aquellas palabras que están contenidas ya sea

en L1 o en L2

El complemento ~L1 consiste en todas aquellas palabras producibles sobre el

alfabeto de L1 que no están ya contenidas en L1

El cociente L1/L2 consiste de todas aquellas palabras v para las cuales existe

una palabra w en L2 tales que vw se encuentra en L1

La estrella L1* consiste de todas aquellas palabras que pueden ser escritas de la

forma W1W2...Wn donde todo Wi se encuentra en L1 y n ≥ 0. (Nótese que esta

definición incluye a ε en cualquier L*)

La intercalación L1*L2 consiste de todas aquellas palabras que pueden ser

escritas de la forma v1w1v2w2...vnwn; son palabras tales que la concatenación

v1...vn está en L1, y la concatenación w1...wn está en L2

Por contraposición al lenguaje propio de los seres vivos y en especial el lenguaje

humano, considerados lenguajes naturales, se denomina lenguaje formal a los

lenguajes «artificiales» propios de las matemáticas o la informática, los lenguajes

artificiales son llamados lenguajes formales (incluyendo lenguajes de programación).

Sin embargo, el lenguaje humano tiene una característica que no se encuentra en los

lenguajes de programación: la diversidad.

En 1956, Noam Chomsky creó la Jerarquía de Chomsky para organizar los distintos

tipos de lenguaje formal.

Page 33: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 33 ~

Verdades concernientes a los lenguajes formales

Teorema 1: El conjunto de lenguajes en general (incluyendo los no-formales) es

incontable.

Lema 1: El conjunto de lenguajes en un alfabeto no vacío dado es incontable

Afirmar que un alfabeto es no-vacío equivale a que ese alfabeto contenga al menos un

símbolo, Basta demostrar que el conjunto de lenguajes en el alfabeto es

incontable. Como sabemos, un lenguaje L en es un subconjunto de , esto nos

lleva a la conclusión de que, el conjunto de todos los lenguajes en es justamente

(el conjunto de todos los subconjuntos o conjunto potencia de ) y es

evidente que es infinito (de hecho; contable), también ha sido demostrado que si

A es un conjunto infinito (contable o incontable), entonces 2A es mayor que A porque

2A pasa a ser un conjunto infinito de ordenes del infinito, al ser mayor, no existirá

biyección entre A y 2A, lo que hace a 2A un conjunto infinito incontable.

Demostración del Teorema 1: Puede derivarse fácilmente que la aseveración

delineada en el Teorema 1 es verdadera, porque el conjunto de lenguajes en general

es justamente una unión infinita de conjuntos del tipo 2A, donde A es un conjunto

infinito contable.

Teorema 2: Los lenguajes son conjuntos contables

Se sabe que un lenguaje L en un alfabeto Σ es un subconjunto de Σ * y como ya se

hizo mención, Σ * es infinito incontable, por ende, L es como mucho un conjunto

infinito incontable (del mismo tamaño que Σ *.

Teorema 3: El conjunto de lenguajes formales es contable

Como sabemos un lenguaje formal puede ser generado por una gramática formal (o

de estructura de frase), lo cual implica que todo lenguaje formal puede ser aceptado

por una Máquina de Turing(MT), lo que a su vez implica que se puede definir una

biyección entre el conjunto de lenguajes formales y el conjunto de las MT´s (debido a

la propiedad transitiva de la relación "existe biyección entre A y B"). Para demostrar el

teorema se utilizará el concepto de codificación de MT´s que se introduce en el

estudio de las MT´s universales, generalmente se codifica una MT con una función que

Page 34: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 34 ~

tiene precisamente como dominio al conjunto de las MT´s (lo llamaremos X) y como

codominio , esa función puede ser una biyección si el codominio pasa a ser Y

(un subconjunto de ) y como es contable, ese subconjunto también

será contable y como existe dicha biyección (entre X e Y).

Gramática formal

Una gramática formal es un objeto o modelo matemático que permite especificar un

lenguaje o lengua, es decir, es el conjunto de reglas capaces de generar todas las

posibilidades combinatorias de ese lenguaje, ya sea éste un lenguaje formal o un

lenguaje natural.

Introducción

El elemento en mayúsculas es el símbolo inicial. Los elementos en minúsculas son

símbolos terminales. Las cadenas de la lengua son aquellas que solo contienen

elementos terminales, como por ejemplo:

bbbdeccc, de, bdec, ... Estas serían tres posibles realizaciones del lenguaje cuya

gramática hemos definido con dos reglas.

Para comprender mejor el concepto pondremos algunas reglas de la gramática

castellana:

Una FRASE se puede componer de SUJETO + PREDICADO

O = SN + SV

Un SUJETO se puede componer de un ARTÍCULO + NOMBRE o SUSTANTIVO

(núcleo) + Complementos

SN = Det + N + C

Un PREDICADO se puede componer de un VERBO conjugado

SV = Aux + GV

Un ARTICULO puede ser la palabra "el"

Un NOMBRE o SUBSTANTIVO puede ser "niño"

Vemos que existen unas definiciones especiales como FRASE, SUJETO, etc. que no

aparecen en la frase final formada. Son unas entidades abstractas denominadas

Categorías Sintácticas que no son utilizables en una frase.

Page 35: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 35 ~

Las categorías sintácticas definen la estructura del lenguaje representando porciones

más o menos grandes de las frases. Existe una jerarquía interna entre las categorías

sintácticas.

La categoría superior sería la FRASE que representa una oración válida en lengua

castellana.

Por debajo de ella se encuentran sus componentes. Ninguna de estas categorías da

lugar a frases válidas solo la categoría superior.

Al finalizar toda la jerarquía llegamos a las palabras que son las unidades mínimas con

significado que puede adoptar una frase.

Aplicando las jerarquías y sustituyendo elementos, llegamos al punto en donde todas

las categorías sintácticas se han convertido en palabras, obteniendo por tanto una

oración VÁLIDA. (Como por ejemplo: El niño corre). Este proceso se llama producción

o generación.

En resumen:

Elementos constituyentes

Una gramática formal es un modelo matemático compuesto por una serie de

categorías sintácticas que se combinan entre sí por medio de unas reglas

sintácticas que definen cómo se crea una categoría sintáctica por medio de

otras o símbolos de la gramática.

Existe una única categoría superior que denota cadenas completas y válidas.

Mecanismos de especificación

Por medio de estos elementos constituyentes se define un mecanismo de

especificación consistente en repetir el mecanismo de sustitución de una

categoría por sus constituyentes en función de las reglas comenzando por la

categoría superior y finalizando cuando la oración ya no contiene ninguna

categoría.

De esta forma, la gramática puede generar o producir cada una de las cadenas del

lenguaje correspondiente y solo estas cadenas.

Page 36: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 36 ~

Definición

Una Gramática Formal es una cuádrupla donde:

N es un alfabeto de símbolos no terminales (variables).

T es un alfabeto de símbolos terminales (constantes).

Debe cumplirse que . denotaremos con el alfabeto de

la gramática.

es el símbolo inicial o axioma de la gramática.

es el conjunto de reglas de producción, de la forma { α → β | α

β }

Es decir, la cadena α debe contener al menos una variable, que puede estar rodeada

de un contexto.

Derivaciones

Sea G = (N,T,P,S) una gramática, y sean α, β, δ, φ, ρ, ... palabras de Σ * . Entonces

β se deriva de α en un paso de derivación, y lo denotamos con α β si existen

dos cadenas , y una producción δ → ρ tales que α = φ1 δ φ2, y β =

φ1 ρ φ2

Notamos con al cierre reflexivo y transitivo de . Es decir α β denota a

una secuencia de derivaciones en un número finito de pasos desde α hasta β.

es una forma sentencial de G, si puede obtenerse la siguiente

secuencia de derivaciones . En el caso particular de que se dice

que x es una sentencia

Se denomina lenguaje formal generado por G al conjunto

Page 37: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 37 ~

EXPRESIÓN REGULAR

Una expresión regular, a menudo llamada también patrón, es una expresión que

describe un conjunto de cadenas sin enumerar sus elementos. Por ejemplo, el grupo

formado por las cadenas Handel, Händel y Haendel se describe mediante el patrón

"H(a|ä|ae)ndel". La mayoría de las formalizaciones proporcionan los siguientes

constructores: una expresión regular es una forma de representar a los lenguajes

regulares (finitos o infinitos) y se construye utilizando caracteres del alfabeto sobre el

cual se define el lenguaje. Específicamente, las expresiones regulares se construyen

utilizando los operadores unión concatenación y clausura de Kleene.

alternación

Una barra vertical separa las alternativas. Por ejemplo, "marrón|castaño" casa

con marrón o castaño.

cuantificación

Un cuantificador tras un carácter especifica la frecuencia con la que éste puede

ocurrir. Los cuantificadores más comunes son +, ? y *:

+

El signo más indica que el carácter al que sigue debe aparecer al menos una

vez. Por ejemplo, "ho+la" describe el conjunto infinito hola, hoola, hooola,

hoooola, etcétera.

?

El signo de interrogación indica que el carácter al que sigue puede aparecer

como mucho una vez. Por ejemplo, "ob?scuro" casa con oscuro y obscuro.

*

El asterisco indica que el carácter al que sigue puede aparecer cero, una, o más

veces. Por ejemplo, "0*42" casa con 42, 042, 0042, 00042, etcétera.

agrupación

Los paréntesis pueden usarse para definir el ámbito y precedencia de los demás

operadores. Por ejemplo, "(p|m)adre" es lo mismo que "padre|madre", y

"(des)?amor" casa con amor y con desamor.

Los constructores pueden combinarse libremente dentro de la misma expresión, por lo

que "H(ae?|ä)ndel" equivale a "H(a|ae|ä)ndel".

Page 38: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 38 ~

La sintaxis precisa de las expresiones regulares cambia según las herramientas y

aplicaciones consideradas, y se describe con más detalle a continuación.

Su utilidad más obvia es la de describir un conjunto de cadenas, lo que resulta de

utilidad en editores de texto y aplicaciones para buscar y manipular textos. Muchos

lenguajes de programación admiten el uso de expresiones regulares con este fin. Por

ejemplo, Perl tiene un potente motor de expresiones regulares directamente incluido

en su sintaxis. Las herramientas proporcionadas por las distribuciones de Unix

(incluyendo el editor sed y el filtro grep) fueron las primeras en popularizar el

concepto de expresión regular.

Aplicaciones

Numerosos editores de texto y otras utilidades (especialmente en el sistema operativo

UNIX/linux), como por ejemplo sed y awk, utilizan expresiones regulares para, por

ejemplo, buscar palabras en el texto y reemplazarlas con alguna otra cadena de

caracteres.

Las expresiones regulares en programación

En el área de la programación las expresiones regulares son un método por medio del

cual se pueden realizar búsquedas dentro de cadenas de caracteres. Sin importar si la

búsqueda requerida es de dos caracteres en una cadena de 10 o si es necesario

encontrar todas las apariciones de un patrón definido de caracteres en un archivo de

millones de caracteres, las expresiones regulares proporcionan una solución para el

problema. Adicionalmente, un uso derivado de la búsqueda de patrones es la

validación de un formato específico en una cadena de caracteres dada, como por

ejemplo fechas o identificadores.

Para poder utilizar las expresiones regulares al programar es necesario tener acceso a

un motor de búsqueda con la capacidad de utilizarlas. Es posible clasificar los motores

disponibles en dos tipos: Motores para el programador y Motores para el usuario final.

Motores para el usuario final: son programas que permiten realizar búsquedas

sobre el contenido de un archivo o sobre un texto extraído y colocado en el programa.

Están diseñados para permitir al usuario realizar búsquedas avanzadas usando este

mecanismo, sin embargo es necesario aprender a redactar expresiones regulares

adecuadas para poder utilizarlos eficientemente. Éstos son algunos de los programas

disponibles:

Page 39: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 39 ~

grep: programa de los sistemas operativos Unix/Linux

PowerGrep: versión de grep para los sistemas operativos Windows

RegexBuddy: ayuda a crear las expresiones regulares en forma interactiva y

luego le permite al usuario usarlas y guardarlas.

EditPad Pro: permite realizar búsquedas con expresiones regulares sobre

archivos y las muestra por medio de código de colores para facilitar su lectura y

comprensión.

Motores para el programador: permiten automatizar el proceso de búsqueda de

modo que sea posible utilizarlo muchas veces para un propósito específico. Estas son

algunas de las herramientas de programación disponibles que ofrecen motores de

búsqueda con soporte a expresiones regulares:

Java: existen varias librerías hechas para java que permiten el uso de RegEx, y

Sun planea dar soporte a estas desde el SDK

JavaScript: a partir de la versión 1.2 (ie4+, ns4+) JavaScript tiene soporte

integrado para expresiones regulares, lo que significa que las validaciones que

se realizan normalmente en una página web podrían simplificarse grandemente

si el programador supiera utilizar esta herramienta.

Perl: es el lenguaje que hizo crecer a las expresiones regulares en el ámbito de

la programación hasta llegar a lo que son hoy en día.

PCRE: librería de ExReg para C, C++ y otros lenguajes que puedan utilizar

librerías dll (Visual Basic 6 por ejemplo).

PHP: tiene dos tipos diferentes de expresiones regulares disponibles para el

programador.

Python: lenguaje de "scripting" popular con soporte a Expresiones Regulares.

.Net Framework: provee un conjunto de clases mediante las cuales es posible

utilizar expresiones regulares para hacer búsquedas, reemplazar cadenas y

validar patrones.

Nota: de las herramientas mencionadas con anterioridad se utilizan el EditPad Pro y

el .Net Framework para dar ejemplos, aunque es posible utilizar las expresiones

regulares con cualquier combinación de las herramientas mencionadas. Aunque en

general las Expresiones Regulares utilizan un lenguaje común en todas las

herramientas, las explicaciones prácticas acerca de la utilización de las herramientas y

los ejemplos de código deben ser interpretados de forma diferente. También es

Page 40: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 40 ~

necesario hacer notar que existen algunos detalles de sintaxis de las expresiones

regulares que son propietarios del .Net Framework que se utilizan en forma diferente

en las demás herramientas de programación. Cuando estos casos se den se hará

notar en forma explícita para que el lector pueda buscar información respecto a estos

detalles en fuentes adicionales. En el futuro se incluirán adicionalmente ejemplos de

otras herramientas y lenguajes de programación.

Expresiones regulares como motor de búsqueda

Las expresiones regulares permiten encontrar porciones específicas de texto dentro de

una cadena más grande de caracteres. Así, si es necesario encontrar el texto "lote" en

la expresión "el ocelote salto al lote contiguo" cualquier motor de búsqueda sería

capaz de efectuar esta labor. Sin embargo, la mayoría de los motores de búsqueda

encontrarían también el fragmento "lote" de la palabra "ocelote", lo cual podría no ser

el resultado esperado. Algunos motores de búsqueda permiten adicionalmente

especificar que se desea encontrar solamente palabras completas, solucionando este

problema. Las expresiones regulares permiten especificar todas estas opciones

adicionales y muchas otras sin necesidad de configurar opciones adicionales, sino

utilizando el mismo texto de búsqueda como un lenguaje que permite enviarle al

motor de búsqueda exactamente lo que deseamos encontrar en todos los casos, sin

necesidad de activar opciones adicionales al realizar la búsqueda.

Expresiones regulares como lenguaje

Para especificar opciones dentro del texto a buscar se utiliza un lenguaje o convención

mediante el cual se le transmite al motor de búsqueda el resultado que se desea

obtener. Este lenguaje le da un significado especial a una serie de caracteres. Por lo

tanto cuando el motor de búsqueda de expresiones regulares encuentre estos

caracteres no los buscará en el texto en forma literal, sino que buscará lo que los

caracteres significan. A estos caracteres se les llama algunas veces "meta-caracteres".

A continuación se listan los principales meta-caracteres y su función y como los

interpreta el motor de expresiones regulares.

Page 41: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 41 ~

Descripción de las expresiones regulares

El Punto "."

El punto es interpretado por el motor de búsqueda como cualquier otro carácter

excepto los caracteres que representan un salto de línea, a menos que se le

especifique esto al motor de Expresiones Regulares. Por lo tanto si esta opción se

deshabilita en el motor de búsqueda que se utilice, el punto le dirá al motor que

encuentre cualquier carácter incluyendo los saltos de línea. En la herramienta EditPad

Pro esto se hace por medio de la opción "punto corresponde a nueva línea" en las

opciones de búsqueda. En .Net Framework se utiliza la opción RegexOptions.

Singleline al efectuar la búsqueda o crear la expresión regular.

El punto se utiliza de la siguiente forma: Si se le dice al motor de RegEx que busque

"g.t" en la cadena "el gato de piedra en la gótica puerta de getisboro goot" el motor

de búsqueda encontrará "gat", "gót" y por último "get". Nótese que el motor de

búsqueda no encuentra "goot"; esto es porque el punto representa un solo carácter y

únicamente uno. Si es necesario que el motor encuentre también la expresión "goot",

será necesario utilizar repeticiones, las cuales se explican más adelante.

Aunque el punto es muy útil para encontrar caracteres que no conocemos, es

necesario recordar que corresponde a cualquier carácter y que muchas veces esto no

es lo que se requiere. Es muy diferente buscar cualquier carácter que buscar cualquier

carácter alfanumérico o cualquier dígito o cualquier no-dígito o cualquier no-

alfanumérico. Se debe tomar esto en cuenta antes de utilizar el punto y obtener

resultados no deseados.

La barra inversa o contrabarra "\"

Se utiliza para "marcar" el siguiente carácter de la expresión de búsqueda de forma

que este adquiera un significado especial o deje de tenerlo. O sea, la barra inversa no

se utiliza nunca por sí sola, sino en combinación con otros caracteres. Al utilizarlo por

ejemplo en combinación con el punto "\." este deja de tener su significado normal y

se comporta como un carácter literal.

De la misma forma, cuando se coloca la barra inversa seguida de cualquiera de los

caracteres especiales que discutiremos a continuación, estos dejan de tener su

significado especial y se convierten en caracteres de búsqueda literal.

Page 42: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 42 ~

Como ya se mencionó con anterioridad, la barra inversa también puede darle

significado especial a caracteres que no lo tienen. A continuación hay una lista de

algunas de estas combinaciones:

\t — Representa un tabulador.

\r — Representa el "regreso al inicio" o sea el lugar en que la línea vuelve a

iniciar.

\n — Representa la "nueva línea" el carácter por medio del cual una línea da

inicio. Es necesario recordar que en Windows es necesaria una combinación de

\r\n para comenzar una nueva línea, mientras que en Unix solamente se usa

\n.

\a — Representa una "campana" o "beep" que se produce al imprimir este

carácter.

\e — Representa la tecla "Esc" o "Escape"

\f — Representa un salto de página

\v — Representa un tabulador vertical

\x — Se utiliza para representar caracteres ASCII o ANSI si conoce su código.

De esta forma, si se busca el símbolo de derechos de autor y la fuente en la

que se busca utiliza el conjunto de caracteres Latin-1 es posible encontrarlo

utilizando "\xA9".

\u — Se utiliza para representar caracteres Unicode si se conoce su código.

"\u00A2" representa el símbolo de centavos. No todos los motores de

Expresiones Regulares soportan Unicode. El .Net Framework lo hace, pero el

EditPad Pro no, por ejemplo.

\d — Representa un dígito del 0 al 9.

\w — Representa cualquier carácter alfanumérico.

\s — Representa un espacio en blanco.

\D — Representa cualquier carácter que no sea un dígito del 0 al 9.

\W — Representa cualquier carácter no alfanumérico.

\S — Representa cualquier carácter que no sea un espacio en blanco.

\A — Representa el inicio de la cadena. No un carácter sino una posición.

\Z — Representa el final de la cadena. No un carácter sino una posición.

Page 43: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 43 ~

\b — Marca el inicio y el final de una palabra.

\B — Marca la posición entre dos caracteres alfanuméricos o dos no-

alfanuméricos.

Nota: La utilidad Charmap.exe de Windows permite encontrar los códigos

ASCII/ANSI/UNICODE para utilizarlos en Expresiones Regulares.

Los corchetes "[]"

La función de los corchetes en el lenguaje de las expresiones regulares es representar

"clases de caracteres", o sea, agrupar caracteres en grupos o clases. Son útiles

cuando es necesario buscar uno de un grupo de caracteres. Dentro de los corchetes es

posible utilizar el guión "-" para especificar rangos de caracteres. Adicionalmente, los

metacaracteres pierden su significado y se convierten en literales cuando se

encuentran dentro de los corchetes. Por ejemplo, como vimos en la entrega anterior

"\d" nos es útil para buscar cualquier carácter que represente un dígito. Sin embargo

esta denominación no incluye el punto "." que divide la parte decimal de un número.

Para buscar cualquier carácter que representa un dígito o un punto podemos utilizar la

expresión regular "[\d.]". Como se hizo notar anteriormente, dentro de los corchetes,

el punto representa un carácter literal y no un metacaracter, por lo que no es

necesario antecederlo con la barra inversa. El único carácter que es necesario

anteceder con la barra inversa dentro de los corchetes es la propia barra inversa. La

expresión regular "[\dA-Fa-f]" nos permite encontrar dígitos hexadecimales. Los

corchetes nos permiten también encontrar palabras aún si están escritas de forma

errónea, por ejemplo, la expresión regular "expresi[oó]n" permite encontrar en un

texto la palabra "expresión" aunque se haya escrito con o sin tilde. Es necesario

aclarar que sin importar cuantos caracteres se introduzcan dentro del grupo por medio

de los corchetes, el grupo solo le dice al motor de búsqueda que encuentre un solo

carácter a la vez, es decir, que "expresi[oó]n" no encontrará "expresioon" o

"expresioón".

La barra "|"

Sirve para indicar una de varias opciones. Por ejemplo, la expresión regular "a|e"

encontrará cualquier "a" o "e" dentro del texto. La expresión regular

"este|oeste|norte|sur" permitirá encontrar cualquiera de los nombres de los puntos

Page 44: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 44 ~

cardinales. La barra se utiliza comúnmente en conjunto con otros caracteres

especiales

El signo de dólar "$"

Representa el final de la cadena de caracteres o el final de la línea, si se utiliza el

modo multi-línea. No representa un carácter en especial sino una posición. Si se

utiliza la expresión regular "\.$" el motor encontrará todos los lugares donde un punto

finalice la línea, lo que es útil para avanzar entre párrafos

El acento circunflejo "^"

Este carácter tiene una doble funcionalidad, que difiere cuando se utiliza

individualmente y cuando se utiliza en conjunto con otros caracteres especiales. En

primer lugar su funcionalidad como carácter individual: el carácter "^" representa el

inicio de la cadena (de la misma forma que el signo de dólar "$" representa el final de

la cadena). Por tanto, si se utiliza la expresión regular "^[a-z]" el motor encontrará

todos los párrafos que den inicio con una letra minúscula. Cuando se utiliza en

conjunto con los corchetes de la siguiente forma "[^\w ]" permite encontrar cualquier

carácter que NO se encuentre dentro del grupo indicado. La expresión indicada

permite encontrar, por ejemplo, cualquier carácter que no sea alfanumérico o un

espacio, es decir, busca todos los símbolos de puntuación y demás caracteres

especiales.

La utilización en conjunto de los caracteres especiales "^" y "$" permite realizar

validaciones en forma sencilla. Por ejemplo "^\d$" permite asegurar que la cadena a

verificar representa un único dígito, "^\d\d/\d\d/\d\d\d\d$" permite validar una fecha

en formato corto, aunque no permite verificar si es una fecha válida, ya que

99/99/9999 también sería válido en este formato; la validación completa de una fecha

también es posible mediante expresiones regulares, como se ejemplifica más

adelante.

Los paréntesis"()"

De forma similar que los corchetes, los paréntesis sirven para agrupar caracteres, sin

embargo existen varias diferencias fundamentales entre los grupos establecidos por

medio de corchetes y los grupos establecidos por paréntesis:

Los caracteres especiales conservan su significado dentro de los paréntesis.

Page 45: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 45 ~

Los grupos establecidos con paréntesis establecen una "etiqueta" o "punto de

referencia" para el motor de búsqueda que puede ser utilizada posteriormente

como se denota más adelante.

Utilizados en conjunto con la barra "|" permite hacer búsquedas opcionales. Por

ejemplo la expresión regular "al (este|oeste|norte|sur) de" permite buscar

textos que den indicaciones por medio de puntos cardinales, mientras que la

expresión regular "este|oeste|norte|sur" encontraría "este" en la palabra

"esteban", no pudiendo cumplir con este propósito.

Utilizado en conjunto con otros caracteres especiales que se detallan

posteriormente, ofrece funcionalidad adicional

El signo de interrogación "?"

El signo de pregunta tiene varias funciones dentro del lenguaje de las expresiones

regulares. La primera de ellas es especificar que una parte de la búsqueda es

opcional. Por ejemplo, la expresión regular "ob?scuridad" permite encontrar tanto

"oscuridad" como "obscuridad". En conjunto con los parentesis redondos permite

especificar que un conjunto mayor de caracteres es opcional; por ejemplo

"Nov(\.|iembre|ember)?" permite encontrar tanto "Nov" como "Nov.", "Noviembre" y

"November". Como se mencionó anteriormente los paréntesis nos permiten establecer

un "punto de referencia" para el motor de búsqueda, sin embargo, algunas veces, no

se desea utilizarlos con este propósito, como en el ejemplo anterior

"Nov(\.|iembre|ember)?". En este caso el establecimiento de este punto de referencia

(que se detalla más adelante) representa una inversión inútil de recursos por parte del

motor de búsqueda. Para evitar se puede utilizar el signo de pregunta de la siguiente

forma: "Nov(?:\.|iembre|ember)?". Aunque el resultado obtenido será el mismo, el

motor de búsqueda no realizará una inversión inútil de recursos en este grupo, sino

que lo ignorará. Cuando no sea necesario reutilizar el grupo, es aconsejable utilizar

este formato. De forma similar, es posible utilizar el signo de pregunta con otro

significado: Los paréntesis definen grupos "anónimos", sin embargo el signo de

pregunta en conjunto con los paréntesis triangulares "<>" permite "nombrar" estos

grupos de la siguiente forma: "^(?<Día>\d\d)/(?<Mes>\d\d)/(?<Año>\d\d\d\d)$";

Con lo cual se le especifica al motor de búsqueda que los primeros dos dígitos

encontrados llevarán la etiqueta "Día", los segundos la etiqueta "Mes" y los últimos

cuatro dígitos llevarán la etiqueta "Año".

Page 46: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 46 ~

Nota: a pesar de la complejidad y flexibilidad dada por los caracteres especiales

estudiados hasta ahora, en su mayoría nos permiten encontrar solamente un caractér

a la vez, o un grupo de caracteres a la vez. Los metacaracteres enumerados en

adelante permiten establecer repeticiones

Las llaves "{}"

Comúnmente las llaves son caracteres literales cuando se utilizan por separado en

una expresión regular. Para que adquieran su función de metacaracteres es necesario

que encierren uno o varios números separados por coma y que estén colocados a la

derecha de otra expresión regular de la siguiente forma: "\d{2}" Esta expresión le

dice al motor de búsqueda que encuentre dos dígitos contiguos. Utilizando esta

fórmula podríamos convertir el ejemplo "^\d\d/\d\d/\d\d\d\d$" que servía para

validar un formato de fecha en "^\d{2}/\d{2}/\d{4}$" para una mayor claridad en la

lectura de la expresión.

Nota: aunque esta forma de encontrar elementos repetidos es muy útil, algunas veces

no se conoce con claridad cuantas veces se repite lo que se busca o su grado de

repetición es variable. En estos casos los siguientes metacaracteres son útiles.

El asterisco "*"

El asterisco sirve para encontrar algo que se encuentra repetido 0 o más veces. Por

ejemplo, utilizando la expresión "[a-zA-Z]\d*" será posible encontrar tanto "H" como

"H1", "H01", "H100" y "H1000", es decir, una letra seguida de un número indefinido

de dígitos. Es necesario tener cuidado con el comportamiento del asterisco, ya que

este por defecto trata de encontrar la mayor cantidad posible de caracteres que

correspondan con el patrón que se busca. De esta forma si se utiliza "\(.*\)" para

encontrar cualquier cadena que se encuentre entre paréntesis y se lo aplica sobre el

texto "Ver (Fig. 1) y (Fig. 2)" se esperaría que el motor de búsqueda encuentre los

textos "(Fig. 1)" y "(Fig. 2)", sin embargo, debido a esta característica, en su lugar

encontrará el texto "(Fig. 1) y (Fig. 2)". Esto sucede porque el asterisco le dice al

motor de búsqueda que llene todos los espacios posibles entre dos paréntesis. Para

obtener el resultado deseado se debe utilizar el asterisco en conjunto con el signo de

pregunta de la siguiente forma: "\(.*?\)" Esto es equivalente a decirle al motor de

búsqueda que "Encuentre un paréntesis de apertura y luego encuentre cualquier

carácter repetido hasta que encuentre un paréntesis de cierre”

Page 47: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 47 ~

El signo de suma "+"

Se utiliza para encontrar una cadena que se encuentre repetida 1 o más veces. A

diferencia del asterisco, la expresión "[a-zA-Z]\d+" encontrará "H1" pero no

encontrará "H". También es posible utilizar este metacaracter en conjunto con el signo

de pregunta para limitar hasta donde se efectúa la repetición

Grupos anónimos

Los grupos anónimos se establecen cada vez que se encierra una expresión regular en

paréntesis, por lo que la expresión "<([a-zA-Z]\w*?)>" define un grupo anónimo que

tendrá como resultado que el motor de búsqueda almacenará una referencia al texto

que corresponda a la expresión encerrada entre los paréntesis.

La forma más inmediata de utilizar los grupos que se definen es dentro de la misma

expresión regular, lo cual se realiza utilizando la barra inversa "\" seguida del número

del grupo al que se desea hacer referencia de la siguiente forma: "<([a-zA-

Z]\w*?)>.*?</\1>" Esta expresión regular encontrará tanto la cadena "Esta" como la

cadena "prueba" en el texto "Esta es una prueba" a pesar de que la expresión no

contiene los literales "font" y "B".

Page 48: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 48 ~

AUTOMATAS

Autómata del griego automatos (αὐτόματος) que significa espontáneo o con

movimiento propio, puede referirse a:

Autómata programable: Equipo electrónico programable en lenguaje no

informático y diseñado para controlar, en tiempo real y en ambiente industrial,

procesos secuenciales.

Teoría de autómatas: Estudio matemático de máquinas abstractas. (p.e.

Autómata finito, autómata con pila)

Autómata (mecánico): Máquina que imita la figura y los movimientos de un

ser animado.

Robot: Máquina o ingenio electrónico programable, capaz de manipular objetos

y realizar operaciones antes reservadas solo a las personas.

Teoría de autómatas

La teoría de autómatas es una rama de las ciencias de la computación que estudia

matemáticamente máquinas abstractas y problemas que éstas son capaces de

resolver. La teoría de autómatas esta estrechamente relacionada con la teoría del

lenguaje formal ya que los autómatas son clasificados a menudo por la clase de

lenguajes formales que son capaces de reconocer.

Un autómata es un modelo matemático para una máquina de estado finita (FSM sus

siglas en inglés). Una FSM es una máquina que, dada una entrada de símbolos, "salta"

a través de una serie de estados de acuerdo a una función de transición (que puede

ser expresada como una tabla). En la variedad común "Mealy" de FSMs, esta función

de transición dice al autómata a que estado cambiar dados un determinado estado y

símbolo.

La entrada es leída símbolo por símbolo, hasta que es "consumida" completamente

(piense en esta como una cinta con una palabra escrita en ella, que es leída por por

una cabeza lectora del autómata; la cabeza se mueve a lo largo de la cinta, leyendo

un símbolo a la vez) una vez la entrada se ha agotado, el autómata se detiene.

Dependiendo del estado en el que el autómata para se dice que este a aceptado o

rechazado la entrada. Si este termina en el estado "acepta", el autómata acepta la

palabra. Si lo hace en el estado "rechaza", el autómata rechazó la palabra, el conjunto

de todas las palabras aceptadas por el autómata constituyen el lenguaje aceptado por

el mismo.

Page 49: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 49 ~

HERRAMIENTAS PARA COMPILADORES

Un analizador léxico también es conocido como escáner; pues su funcionalidad es la

de analizar el lexema de las palabras o cadenas de caracteres sobre un patrón

definido.

Es decir; 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

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 correcto o no gramaticalmente.

Algunas de las unidades de léxico no son empleadas por el analizador gramatical sino

que son descartadas o filtradas. 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.

Algunos generadores de Analizadores Léxico…

LEX Código generado: C.

FLEX Código generado: C++.

ZLEX Código generado: C.,

Soporta códigos de caracteres de 16 bits.

JAX Código generado: Java.

No soporta entornos, está basado en expresiones regulares.

No soporta Unicode.

JLEX Código generado: Java.

Similar a lex.

Diseñado para ser usado junto con CUP.

JFLEX Código generado: Java.

Diseñado para ser usado junto con CUP.

Page 50: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 50 ~

LEX:

Recibe la especificación de las expresiones regulares de los patrones que representan

a los tokens del lenguaje y las acciones a tomar cuando los detecte.

Genera los diagramas de transición de estados en código C, C ++, o Java

generalmente.

Ventajas: Comodidad de Desarrollo.

Desventajas:

1. El mantenimiento del código generado resulta complicado.

2. La eficiencia del código generado depende del generador.

Parte de un conjunto de reglas léxicas (expresiones regulares) y produce un programa

(yylex) que reconoce las cadenas que cumplen dichas reglas.

1. Yylex es la implementación del Autómata Finito Determinista.

FLEX:

Flex es una herramienta para generar escáneres: programas que reconocen patrones

léxicos en un texto. flex lee los ficheros de entrada dados, o la entrada estándar si no

se le ha indicado ningún nombre de fichero, con la descripción de un escáner a

generar. La descripción se encuentra en forma de parejas de expresiones regulares y

código C, denominadas reglas. flex genera como salida un fichero fuente en C,

`lex.yy.c', que define una rutina `yylex()'. Este fichero se compila y se enlaza con la

librería `-lfl' para producir un ejecutable. Cuando se arranca el fichero ejecutable,

este analiza su entrada en busca de casos de las expresiones regulares. Siempre que

encuentra uno, ejecuta el código C correspondiente

El principal objetivo de diseño de flex es que genere analizadores de alto rendimiento.

Este ha sido optimizado para comportarse bien con conjuntos grandes de reglas.

Aparte de los efectos sobre la velocidad del analizador con las opciones de compresión

de tablas `-C' hay un número de opciones/acciones que degradan el rendimiento.

Flex ofrece dos maneras distintas de generar analizadores para usar con C++. La

primera manera es simplemente compilar un analizador generado por flex usando un

compilador de C++ en lugar de un compilador de C. No debería encontrarse ante

ningún error de compilación Puede entonces usar código C++ en sus acciones de las

reglas en lugar de código C. Fíjese que la fuente de entrada por defecto para su

Page 51: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 51 ~

analizador permanece como yyin, y la repetición por defecto se hace aún a yyout.

Ambos permanecen como variables `FILE *' y no como flujos de C++.

Flex es una reescritura de la herramienta lex del Unix de AT&T (aunque las dos

implementaciones no comparten ningún código), con algunas extensiones e

incompatibilidades, de las que ambas conciernen a aquellos que desean escribir

analizadores aceptables por cualquier implementación. Flex sigue completamente la

especificación POSIX de lex, excepto que cuando se utiliza `%pointer' (por defecto),

una llamada a `unput()' destruye el contenido de yytext, que va en contra de la

especificación POSIX.

http://ditec.um.es/~aflores/dile/flex/flex-es_toc.html#TOC1

JAX:Jax es un compilador léxico creado en lenguaje Java, que genera un escáner a partir

de expresiones regulares que existen por defecto en un archivo de java.

Jax procesa estas expresiones regulares y genera un ficher Java que pueda ser

compilado por Java y así crear el escaner.

Los escaners generados por Jax tienen entradas de búfer de tamaño arbitrario, y es al

menos más conveniente para crear las tablas de tokens, Jax utiliza solo 7 bits de

caracteres ASCII, y no permite código Unario.

http://www.cs.princeton.edu/~ejberk/JavaLex/JavaLex.html

Uno de los aspectos fundamentales que tienen los lenguajes Lex y Yacc es que son

parte importante de un compilador. Pues la unión de estos dos generan un

compilador, claro cada uno de ellos aportando su propio diseño y su forma de ejecutar

sus procesos.

Tanto el analizador léxico como el sintáctico pueden ser escritos en cualquier

lenguaje de programación. A pesar de la habilidad de tales lenguajes de propósito

general como C, lex y yacc son más flexibles y mucho menos complejos de usar.

Page 52: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 52 ~

LENGUAJE LEX

¿QUÉ ES LEX?

Lex es un generador de analizadores léxicos. Cada vez que Lex encuentra un

lexema que viene definido por una expresión regular, se ejecutan las acciones

(escritas en C) que van al lado de la definición de dicha expresión.

Cuando se emplea el término Lex, se mencionan dos posibles significados:

a. Una notación para especificar las características lexicográficas de un

lenguaje de programación,

b. Un traductor de especificaciones lexicográficas.

Esta misma dualidad también es de aplicación al término Yacc.

Lex crea yylex, una variable que contendrá un número, el cual se corresponde

con el token de cada expresión regular. También, instancia una variable global yytext,

que contiene el lexema que acaba de reconocer.

Así, por ejemplo, para el siguiente código fuente de entrada:

%%expresión1 {acción1}expresión2 {acción2}... ...expresiónn

{acciónn}%%

Lex genera un programa en C (generalmente denominado lex.yy.c) que incluye,

entre otras, la función yylex():

Int yylex(){ while(!eof()) {

switch(...) {

case -1: ... ; break;

case 0: ... ; break;

case 1: {acción1}; break; ...

case n: {acciónn}; break; ...

} ...

}...}

Esta función recorre el texto de entrada. Al descubrir algún lexema que se

corresponde con alguna expresión regular, construye el token, y realiza la acción

correspondiente. Cuando se alcanza el fin de fichero, devolverá -1. La función yylex()

podrá ser invocada desde cualquier lugar del programa.

Cómo escribir expresiones regulares en Lex

Page 53: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 53 ~

Deberán cumplir los siguientes requisitos:

Las expresiones regulares (ER, de aquí en adelante) han de aparecer en la

primera columna.

Alfabeto de entrada: Caracteres ASCII 0 al 127.

Concatenación: Sin carácter especial, se ponen los caracteres juntos.

Caracteres normales: Se representan a ellos mismos.

Caracteres especiales: Se les pone la barra '\' delante. Éstos son:

* + ? | [ ] ( ) " \ . { } ^ $ / < >

Caracteres especiales dentro de los corchetes ( '[' y ']' ): - \ ^

Las equivalencias entre ER normales y las que se usan en Lex se muestran con

ejemplos en esta tabla:

Caracteres Ejemplo Significado

Concatenación Xy El patrón consiste en x seguido de y.

Unión x|y El patrón consiste en x o en y.

Repetición x* El patrón consiste en x repetido cero a más

veces.

Clases de

caracteres

[0-9] Alternancia de caracteres en el rango

indicado, en este caso 0|1|2|...|9.

Más de un rango se puede especificar, como

por ejemplo: [0-9A-Za-z] para caracteres

alfanuméricos.

Operador

negación

[^0-9] El primer carácter en una clase de caracteres

deberá ser ^ para indicar el complemento del

conjunto de caracteres especificado. Así,

[^0-9] especifica cualquier carácter que no

sea un dígito.

Carácter

arbitrario

. Con un único carácter, excepto \n.

Repetición

única

x? Cero o una ocurrencia de x.

Repetición no

nula

x+ Una o más ocurrencias de x.

Page 54: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 54 ~

Repetición

especificada

x{n,m} x repetido entre n y m veces.

Comienzo de

línea

^x Unifica x sólo al comienzo de una línea

Fin de línea x$ Unifica x sólo al final de una línea

Sensibilidad al

contexto

(operador "look

ahead")

ab/cd Unifica ab, pero sólo seguido de bc.

Cadenas de

literales

"x" Cuando x tenga un significado especial.

Caracteres

literales

\x Cuando x es un operador que se representa a

él mismo. También para el caso de \n, \t,

etc.

Definiciones {nombre_var} Pueden definirse subpatrones. Esto significa

incluir el patrón predefinido llamado

nombre_var.

Definiciones en Lex

La sección de definiciones permite predefinir cadenas que serán útiles en la

sección de las reglas. Por ejemplo:comentario"//".*limitador [ \t\n]espblanco

{limitador}+letramay [A-Z]letramin [a-z]letra

{letramay}|{letramin}carascii [^\"\n]caresc \\n|\\\"digito [0-9]variable

{letramin}({letramin}|{digito})*entero

{digito}+texto \"{(carascii}|{caresc})*\"

Cada regla está compuesta de un nombre que se define en la parte izquierda, y

su definición se coloca en la derecha. Así, podemos definir comentario como // (con la

barra puesto que es un carácter especial), seguido por un número arbitrario de

caracteres excepto el de fin de línea. Un limitador será un espacio, tabulador o fin de

línea, y un espblanco será uno o más limitadores. Nótese que la definición de

espblanco usa la definición anterior de limitador.

Reglas para eliminar ambigüedades

Page 55: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 55 ~

Se producen cuando varias ER's son aplicables a un mismo lexema reconocido. Lex

seleccionará siempre la ocurrencia más larga posible. Si dos ocurrencias tienen la

misma longitud, tomará la primera.

Formalmente, podemos definir a lex como una herramienta para construir

analizadores léxicos o "lexers". Un lexer lee de un flujo de entrada cualquiera, y la

divide en unidades léxicas (la tokeniza), para ser procesada por otro programa o

como producto final.

La entrada es tomada de yyin, que por defecto su valor es stdin, es decir, la pantalla o

terminal, pero este valor puede ser modificado por cualquier apuntador a un archivo.

También es posible leer la entrada desde un arreglo de caracteres u otros

medios, para cual es necesario implementar algunas funciones de lex mismas que

definiremos en la última parte de esta sección (Agregar Funcionalidad).

Expresiones regulares usadas en lex

Para poder crear expresiones regulares y patrones para las reglas, es necesario

saber que la concatenación de expresiones se logra simplemente juntando dos

expresiones, sin dejar espacio entre ellas y que es bueno declarar una expresión muy

compleja por partes como definiciones, y así evitar tener errores difíciles de encontrar

y corregir.

A continuación una lista de las expresiones regulares mas usadas en lex.

Ops Ejemplo Explicación

[] [a-z] Una clase de Caracteres, coincide con un carácter

perteneciente a la clase, pueden usarse rangos, como en el

ejemplo, cualquier carácter, excepto aquellos especiales o de

control son tomados literalmente, en el caso de los que no,

pueden usarse secuencias de escape como las de C, \t, \n

etcétera.

Si su primer carácter es un "^", entonces coincidirá con

cualquier carácter fuera de la clase.

* [ \n\t]* Todas las cadenas que se puedan formar, se puede decir que

este operador indica que se va a coincidir con cadenas

formadas por ninguna o varias apariciones del patrón que lo

Page 56: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 56 ~

antecede.

El ejemplo coincide con cualquier combinación de símbolos

usados para separar, el espacio, retorno y tabulador.

+ [0-9]+ Todas las cadenas que se puedan formar, excepto cadenas

vacías. En el ejemplo se aceptan a todos los números

naturales y al cero.

. .+ Este es una expresión regular que coincide con cualquier

entrada excepto el retorno de carro ("\n"). El ejemplo acepta

cualquier cadena no vacía.

{} a{3,6} Indica un rango de repetición cuando contiene dos números

separados por comas, como en el ejemplo, la cadena aceptada

será aquella con longitud 3, 4, 5 o 6 formada por el carácter

'a'.

Indica una repetición fija cuando contiene un solo numero, por

ejemplo, a{5}, aceptaría cualquier cadena formada por 5 a's

sucesivas.

En caso de contener un nombre, indica una sustitución por una

declaración en la sección de declaraciones (Revisar el

ejemplo1).

? -?[0-9]+ Indica que el patrón que lo antecede es opcional, es decir,

puede existir o no. En el ejemplo, el patrón coincide con todos

los números enteros, positivos o negativos por igual, ya que el

signo es opcional.

| (-|+|~)?[0-

9]+

Este hace coincidir, al patrón que lo precede o lo antecede y

puede usarse consecutivamente. En el ejemplo tenemos un

patrón que coincidirá con un entero positivo, negativo o con

signo de complemento.

"" "bye" Las cadenas encerradas entre " y " son aceptadas literalmente,

es decir tal como aparecen dentro de las comillas, para incluir

caracteres de control o no imprimibles, pueden usarse dentro

de ellas secuencias de escape de C. En el ejemplo la única

cadena que coincide es 'bye'.

\ \. Indica a lex que el carácter a continuación será tomado

literalmente, como una secuencia de escape, este funciona

para todos los caracteres reservados para lex y para C por

Page 57: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 57 ~

igual. En el ejemplo, el patrón coincide solo con el carácter "."

(punto), en lugar de coincidir con cualquier carácter, como

seria el casi sin el uso de "\".

<<EOF>> [a-z] Solo en flex, este patrón coincide con el fin de archivo.

Ampliación de las expresiones regulares

Las expresiones regulares (propiamente dichas, en un sentido estricto), tal y

como se estudian en la teoría de lenguajes para especificar los lenguajes regulares,

están constituidas por símbolos de un alfabeto Σ, relacionados mediante los operadores

binarios alternativa (|) y concatenación (·) y el operador unitario estrella (*); en la

escritura de una expresión regular también se pueden emplear paréntesis para precisar

el orden de aplicación de los operadores. El asterisco de la operación estrella suele

colocarse como exponente de la parte de la expresión regular afectada.

La precedencia de los operadores es la definida por la siguiente jerarquía,

relacionada de mayor a menor precedencia:

1. operaciones entre paréntesis

2. operador estrella

3. operador concatenación

4. operador alternativa

Así, por ejemplo, son expresiones regulares definidas sobre el alfabeto Σ = {a, b}

b·a·a|b·b

a*·(b|b·a)

La primera denota el lenguaje regular formado por dos palabras {baa, bb} y la

segunda denota el lenguaje regular de infinitas palabras {b, ba, ab, aba, aab, aaba,

}.

Entre los símbolos que aparecen en una expresión regular cabe distinguir los

caracteres y los metacaracteres; los caracteres son los símbolos que pertenecen al

alfabeto sobre el que está definida la expresión regular; los metacaracteres son los

símbolos que no pertenecen a ese alfabeto: los operadores y los paréntesis.

En la escritura de las expresiones regulares el punto representativo de la

concatenación entre símbolos del alfabeto suele suprimirse; de acuerdo con esta

notación simplificada, las anteriores expresiones suelen escribirse así:

baa|bb

a*(b|ba)

Page 58: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 58 ~

Dado que el espacio en blanco no es un símbolo perteneciente al alfabeto Σ sobre

el que están definidas las expresiones regulares anteriores, también podrían escribirse

(sin ocasionar confusión y con la pretensión de favorecer la legibilidad) de esta manera:

baa | bb

a* ( b | ba )

En una especificación Lex se incluyen expresiones regulares, pero escritas con

una notación que es una ampliación de la notación empleada en la definición (en

sentido estricto) anterior. Esta ampliación tiene como principales objetivos:

- hacer más cómoda y escueta la escritura de las expresiones regulares,

- distinguir de manera precisa los caracteres del alfabeto y los metacaracteres

empleados en la escritura de las expresiones regulares.

Sea el alfabeto Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, la expresión regular que denota

las palabras de longitud uno es:

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Con la notación ampliada de las expresiones regulares Lex (empleando unos

nuevos metacaracteres: el guión y los corchetes de abrir y de cerrar), esa misma

expresión puede escribirse así:

[0-9]

Las expresiones regulares de una especificación Lex han de procesarse mediante

un programa y, por ello, han de estar grabadas en un fichero de tipo texto. En estas

condiciones no resulta adecuado el convenio según el cual la operación estrella se

escribe en forma de exponente; por ejemplo, la expresión regular

ab*

Quedaría grabada en el fichero mediante la secuencia de tres caracteres

consecutivos ab*. Si Σ = {+, -, *, /} es el alfabeto sobre el que definen las expresiones

regulares, ¿qué lenguaje denota la expresión regular +* grabada como una secuencia

de dos caracteres? La respuesta depende de si el asterisco se considera como carácter

del alfabeto o como operador (metacarácter).

Las expresiones regulares empleadas en las especificaciones Lex no tienen

conceptualmente ninguna diferencia con las expresiones regulares (en sentido

estricto) que definen los lenguajes formales regulares; lo único que aportan son

modificaciones en la notación empleada en la escritura de las expresiones en forma de

secuencias de caracteres consecutivos susceptibles de grabarse en un fichero de tipo

texto. Esta notación ampliada alcanza cierta dificultad por las siguientes causas:

Para facilitar y acortar la escritura de las expresiones se introducen bastantes

metacaracteres que se intercalan con los caracteres

Page 59: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 59 ~

Es habitual que cualquier carácter del alfabeto ASCII forme parte del alfabeto

sobre el que se definen las expresiones regulares y que, por lo tanto, pueda

aparecer en ellas (incluso el espacio En blanco, el tabulador o el fin de línea).

Se precisa la definición de convenios para distinguir entre los caracteres y los

metacaracteres.

En lo que sigue se emplea la notación para indicar que lo que se pone a

continuación de esos símbolos es la descripción del conjunto de palabras denotadas por

la expresión regular que les precede.

Variables y Funciones, que se usan dentro de un programa generado por lex

FILE *yyin

Este es un apuntador declarado globalmente que apunta al lugar de donde se

van a leer los datos, por ser un file pointer, este solo puede leer de flujos como

archivos, para leer de una cadena es necesario reimplementar el macro input() como

se vera mas adelante.

FILE *yyout

Este es el lugar al que se escriben por default todos los mensajes, al igual que

yyin esta declarado globalmente y es un apuntador.

int input(void)

El objetivo de esta Macro es alimentar a yylex() carácter por carácter, devuelve

el siguiente carácter de la entrada, la intención más común para modificar esta

función, es cambiar el origen de la entrada de manera mas flexible que con yyin, ya

que no solo es posible leer de otro archivo, sino que también es posible leer el flujo

para parsear una cadena cualquiera, o un grupo de cadenas como una línea de

comandos.

Para reimplementar esta macro, es necesario primero eliminarla del archivo por lo que

es necesario incluir un comando del preprocesador de C el sección de declaraciones :

%{

#undef input

%}

y en la parte de subrutinas, implementar nuestro nuevo input() con el prototipo

mostrado anteriormente.

void unput(int)

Page 60: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 60 ~

El objetivo de esta macro, es regresar un carácter a la entrada de datos, es útil

para yylex() tener una de estas, ya que para identificar un patrón puede ser necesario

saber que carácter es el que sigue. La intención de reimplementar esta es

complementar el uso de la reimplementacion de input(), ya que input() y unput()

deben ser congruentes entre si.

Antes de reimplementar esta, también es necesario eliminarla antes usando una

instrucción del preprocesador de C:

%{

#undef unput

%}

int yywrap(void)

Esta función, es auxiliar en el manejo de condiciones de final de archivo, su

misión es proporcionarle al programador la posibilidad de hacer algo con estas

condiciones, como continuar leyendo pero desde otro archivo etcétera.

int yylex(void)

Esta función, es casi totalmente implementada por el usuario en la sección de

reglas, donde como ya vimos, puede agregarse código encerrado entre %{ y %} así

como en las reglas mismas.

int yyleng;

Contiene la longitud del token leido, su valor es equivalente a yyleng =

strlen(yytext);.

char *yytext;

Contiene el token que acaba de ser reconocido, su uso es principalmente dentro

de las reglas, donde es común hacer modificaciones al token que acaba de ser leído o

usarlo con algún otro fin. En el ejemplo 1 este token es usado para dar echo en la

pantalla.

void output(int);

Esta macro, escribe su argumento en yyout.

void yyinput(void);

Es una interfaz para la macro input().

void yyunput(int);

Es una interfaz para la macro unput().

void yyoutput(int);

Page 61: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 61 ~

Es una interfaz para la macro output().

Partes del un programa Lex

Un programa Lex consta de tres secciones:

<declaraciones>

%%

<reglas de traducción>

%%

<procedimientos auxiliares>

La sección de declaraciones incluye declaraciones de variables, constantes y

definiciones regulares. Las definiciones regulares son sentencias usadas como

componentes de las expresiones regulares que aparecen en las reglas.

Las reglas de traducción de un programa Lex son sentencias de la forma:

p1 { acción1 }

p2 { acción2 }

... ...

pn { acciónn }

donde cada pi es una expresión regular y cada accióni es un fragmento de programa,

describiendo qué acción debe realizar el analizador léxico cuando el patrón pi se

corresponde con un lexema. En Lex, las acciones están escritas en C.

La tercera sección contiene cualesquiera procedimientos auxiliares que sean

requeridos por las acciones. Alternativamente, estos procedimientos pueden ser

compilados separadamente y montados junto con el analizador léxico.

Un analizador léxico creado por Lex funciona en concierto con un analizador

sintáctico de la siguiente manera. Cuando es activado por el analizador sintáctico, el

analizador léxico comienza leyendo de su entrada un carácter a la vez, hasta que

encuentre el prefijo más largo de la entrada que ha correspondido con una de las

expresiones regulares pi. Entonces, ejecuta accióni, que típicamente devolverá el

control al parser. Pero, si no lo hace, entonces el analizador léxico procede a buscar

más lexemas, hasta que una acción contenga una sentencia return o se lea el fichero

completo. La búsqueda repetida de lexemas hasta una devolución explícita del control

permite que el analizador léxico procese los espacios en blanco y comentarios

convenientemente. El analizador léxico devuelve un entero, que representa el token,

al analizador sintáctico. Para pasar un valor de atributo con información sobre el

lexema, se puede usar una variable global llamada yylval. Esto se hace cuando se use

Yacc como generador del analizador sintáctico.

Page 62: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 62 ~

Los analizadores léxicos, para ciertas construcciones de lenguajes de

programación, necesitan ver adelantadamente más allá del final de un lexema antes

de que puedan determinar un token con certeza. En Lex, se puede escribir un patrón

de la forma r1/r2, donde r1 y r2 son expresiones regulares, que significa que una

cadena se corresponde con r1, pero sólo si está seguida por una cadena que se

corresponde con r2. La expresión regular r2, después del operador lookahead "/",

indica el contexto derecho para una correspondencia; se usa únicamente para

restringir una correspondencia, no para ser parte de la correspondencia.

Programación de analizadores mediante LEX

Lex suele ser usado según la siguiente figura:

Primero, se prepara una especificación de un analizador léxico creando un

programa contenido, por ejemplo en el fichero prog.l, en lenguaje Lex. Entonces,

prog.l se pasa a través del compilador Lex para producir un programa en C, que por

defecto se denomina lex.yy.c en el sistema operativo UNIX. Éste consiste en una

representación tabular de un diagrama de transición construido a partir de las

expresiones regulares de prog.l, junto con una rutina estándar que usa la tabla de

reconocimiento de lexemas. Las acciones asociadas con expresiones regulares en

prog.l son trozos de código C, y son transcritas directamente a lex.yy.c. Finalmente,

lex.yy.c se pasa a través del compilador C para producir un programa objeto, que por

defecto se llama a.out, el cual es el analizador léxico que transforma una entrada en

una secuencia de tokens.

Page 63: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 63 ~

RECUPERACIÓN DE ERRORES LEXICOGRÁFICOS

Los programas pueden contener diversos tipos de errores, que pueden ser:

Errores lexicográficos: Que veremos a continuación.

Errores sintácticos: Por ejemplo, una expresión aritmética con mayor numero

de paréntesis de apertura que de cierre.

Errores semánticas: Por ejemplo, la aplicación de un operador a un tipo de

datos incompatible con el mismo.

Errores lógicos: Por ejemplo, un bucle sin final.

Cuando se detecta un error, un compilador puede detenerse en ese punto e

informar al usuario, o bien desechar una serie de caracteres del texto fuente y

continuar con el análisis, dando al final una lista completa de todos los errores

detectados. En ciertas ocasiones es incluso posible que el compilador corrija el error,

haciendo una interpretación coherente de los caracteres leídos. En estos casos, el

compilador emite una advertencia, indicando la suposición que ha tomado, y continúa

el proceso sin afectar a las sucesivas fases de compilación.

Los errores lexicográficos se producen cuando el analizador no es capaz de generar un

token tras leer una determinada secuencia de caracteres. En general, puede decirse

que los errores lexicográficos son a los lenguajes de programación lo que las faltas de

ortografía a los lenguajes naturales. Las siguientes situaciones producen con

frecuencia la aparición de errores lexicográficos:

1. Lectura de un carácter que no pertenece al vocabulario terminal previsto para

el autómata. Lo más normal en este caso es que el autómata ignore estos

caracteres extraños y continue el proceso normalmente. Por ejemplo, pueden

dar error en la fase de análisis lexicográfico la inclusión de caracteres de control

de la impresora en el programa fuente para facilitar su listado.

2. Omisión de un carácter. Por ejemplo, si se ha escrito ELS en lugar de ELSE.

3. Se ha introducido un nuevo caracter. Por ejemplo, si escribimos ELSSE en lugar

de ELSE.

4. Han sido permutados dos caracteres en el token analizado. Por ejemplo, si

escribiéramos ESLE en lugar de ELSE.

5. Un carácter ha sido cambiado. Por ejemplo, si se escribiera ELZE en vez de

ELSE.

Las técnicas de recuperación de errores lexicográficos se basan, en general, en la

obtención de los distintos sinónimos de una determinada cadena que hemos detectado

Page 64: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 64 ~

como errónea. Por otra parte, el analizador sintáctico es capaz en muchos casos de

avisar al analizador lexicográfico de cuál es el token que espera que éste lea.

Análogamente, podemos incluir rutinas para los demás casos. Por ejemplo, si el

analizador lee el lexema ESLE, y no puede construir un token correcto para él mismo,

procedería a generar los sinónimos por intercambio de caracteres (es decir, SELE,

ELSE o ESEL) y comprobaría si alguno de ellos es reconocible. En caso afirmativo,

genera el token correspondiente y advierte al usuario del posible error y de su

interpretación automática, continuando con el proceso.

Todos los procedimientos para la recuperación de errores lexicográficos son en la

práctica métodos específicos, y muy dependientes del lenguaje que se pretende

compilar.

Reglas de Lex

Esta sección también puede incluir código de C encerrado por %{ y %}, que será

copiado dentro de la función yylex(), su alcance es local dentro de la misma función.

Las reglas de lex, tienen el siguiente formato :

<Expresión regular><Al menos un espacio>{Código en C}

En el ejemplo podemos ver que :

"bye" {bye();return 0;}

"quit" {bye();return 0;}

"resume" {bye();return 0;}

{Palabra} {printf("Se leyó la palabra : %s", yytext);palabra++;}

{Numero} {printf("Se leyó el numero : %d", atoi(yytext));numero++;}

. printf("%s",yytext[0]);

Como ya vimos en la segunda columna se escriben acciones en C a realizar

cada que se acepta una cadena con ese patrón, misma que es almacenada en un

array apuntado por yytext, podemos ver que las acciones están encerradas entre "{"

y "}" lo que indica que se incluye más de un statement de C por regla, el contra

ejemplo es la ultima regla, que reconoce cualquier carácter y lo imprime a la pantalla

mediante el uso de printf().

Entonces, podemos decir que una regla de lex esta formada por una expresión

regular y la acción correspondiente, típicamente encerrada entre "{" y "}".

Page 65: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 65 ~

Notas complementarias sobre Lex

Cada vez que se realice una de las acciones, la variable char *yytext contendrá

el lexema reconocido.

La variable int yyleng contiene la longitud del lexema reconocido.

Entrada y salida: FILE *yyin, *yyout. Por defecto, se usan los predefinidos en C.

Cuando Lex reconoce el carácter de fin de fichero, llama a la función int

yywrap(), que por defecto devuelve 1. Si devuelve 0, significará que está

disponible una entrada anterior, con lo cuál aún no se habrá terminado la

lectura.

Contextos: Permiten especificar cúando se usarán ciertas reglas. Veremos

mediante un ejemplo de eliminación de comentarios cómo se usan los

contextos:

%start COMENTARIO%%\/\* {BEGIN COMENTARIO;} /* activa

COMENTARIO */<COMENTARIO>\*\/ {BEGIN 0;} /* desactiva

todos */<COMENTARIO>. ; /* nothing to

do! ;-P */[a-zA-Z][a-z0-9]+ ;...%%

Ejemplos en lex

EJEMPLO:

A continuación se presenta un ejemplo que ilustra de manera general el uso de lex

para reconocer patrones de expresiones regulares básicas, que reconoce cualquier

numero entero y cualquier palabra formada por letras mayúsculas de la "a" a la "z",

sin importar si son mayúsculas o minúsculas.

Download

%{

#include

int palabra=0, numero=0;

%}

Numero -?[0-9]+

Palabra [a-zA-Z]+

%%

"bye" {bye();return 0;}

"quit" {bye();return 0;}

"resume" {bye();return 0;}

Page 66: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 66 ~

{Palabra} {printf("Se leyó la palabra : %s", yytext);palabra++;}

{Numero} {printf("Se leyó el numero : %d", atoi(yytext));numero++;}

. printf("%s",yytext[0]);

%%

main(){

printf("ejem1.l\nEste ejemplo, distingue entre un numero entero y

palabras.\n Introduzca bye, quit o resume para terminar.\n");

yylex();

}

bye(){

printf("Se leyeron %d entradas, de las cuales se

reconocieron\n%d\tEnteros\ny\n%d\tPalabras.\n", (palabra+numero), numero,

palabra);

}

En este ejemplo, una de las primeras cosas a notar, son las dos líneas "%%" que

sirven como separadores para las tres secciones de una especificación lex, la primera,

la de definiciones, sirve para definir cosas que se van a usar en el programa

resultante o en la misma especificación:

%{

#include

int palabra=0, numero=0;

%}

Numero -?[0-9]+

Palabra [a-zA-Z]+

Podemos ver dos tipos de declaraciones, declaraciones de C y declaraciones de lex, las

de C son aquellas encerradas entre dos líneas %{ y %} respectivamente que le

indican a lex, cuando se incluye código que será copiado sin modificar al archivo

generado en C (típicamente lex.yy.c).

Page 67: Biblioteca Central de la Universidad Nacional del Santa - …biblioteca.uns.edu.pe/saladocentes/archivoz/publicacio... · 2012-07-11 · ð•La resolución de las direcciones de

Universidad Nacional del Santa Curso: Teoría de Compiladores

Docente: Ing. Mirko Manrique Ronceros ~ 67 ~

Las declaraciones de lex están formadas por un nombre o identificador y su respectiva

expresión regular, su funcionamiento es análogo a aquel del "#define" del

preprocesador de C, cada vez que aparecen es como si en ese lugar estuviera escrita

la expresión regular equivalente, también se pueden usar estas para formar nuevas

expresiones regulares.