Documentos de Académico
Documentos de Profesional
Documentos de Cultura
LENGUAJES Y AUTÓMATAS I
Profesor: Stein Carrillo Juan Manuel
Sabemos que el analizador léxico es parte del compilador que lee el texto aparte de
identificar lexemas, también debe realizar otras tareas, otra de esas tareas es eliminar
comentarios. Y los espacios en blancos como puede ser caracteres de espacio tabulador
y tal vez otros caracteres que se utilizan para separar los tokens. Otra de las tareas y es
correlacionar los mensajes de error que son generados por el compilador con el programa
fuente.
3.1.1 Comparación entre análisis léxico y análisis sintáctico
Hay muchas razones por las que cuales análisis de un compilador se separa en fases de
análisis léxico y análisis sintáctico (parsing).
La sencillez en el diseño es más importante: La separación del análisis léxico y
el análisis sintáctico a menudo nos permite simplificar por lo menos una de estas
tareas.
Mejorar la eficiencia del compilador: Un analizador léxico separado nos permite
aplicar técnicas especializadas que sirven sólo para la tarea léxica, no para el
trabajo del análisis sintáctico.
Mejorar la portabilidad del compilador: Las peculiaridades específicas de los
dispositivos de entrada pueden restringirse al analizador léxico.
3.1.2 Tokens, patrones y lexemas
Cada búfer es del mismo tamaño N, y por lo general N es del tamaño de un bloque de
disco (es decir, 4 096 bytes). Mediante el uso de un comando de lectura del sistema
podemos leer N caracteres y colocarlos en un búfer, en vez de utilizar una llamada al
sistema por cada carácter. Si quedan menos de N caracteres en el archivo de entrada,
entonces un carácter especial, representado por eof, marca el final del archivo fuente y es
distinto a cualquiera de los posibles caracteres del programa fuente.
Se mantienen dos apuntadores a la entrada:
1. El apuntador inicioLexema marca el inicio del lexema actual, cuya extensión
estamos tratando de determinar.
2. El apuntador avance explora por adelantado hasta encontrar una coincidencia en
el patrón
Una vez determinado el siguiente lexema, avance se colocará en el carácter del extremo
derecho, luego una vez registrado el lexema como un valor de atributo de un token
devuelto del analizador sintáctico, iniciolexema será colocado en el carácter después
del lexema que acabamos de encontrar.
3.2.2 Centinelas
Basándonos en el ejemplo anterior debemos de verificar que cada que movamos el
apuntador avance, no hayamos salido de 1 de los búferes, si esto llegara a pasar
entonces debemos de recargar al siguiente. por lo tanto cada lectura de caracteres
deberá tener dos pruebas: una para el final del buffer y la otra para determinar el carácter
que se leerá. Ahora bien podemos hacer una combinación de ambas si tenemos cada
buffer para que contenga un valor centinela al final Se dice que el centinela es aquel
carácter especial que no forma parte del programa fuente para esto una opción natural del
carácter es eof.
Observe que eof retiene su uso como marcador del final de toda la entrada. Cualquier eof
que aparezca en otra ubicación distinta al final de un búfer significa que llegamos al final
de la entrada.
1. ∈ es una expresión regular, y L(∈) es {∈}; es decir, el lenguaje cuyo único miembro es
la cadena vacía.
2. Si a es un símbolo en Σ, entonces a es una expresión regular, y L(a) = {a}, es decir, el
lenguaje con una cadena, de longitud uno, con a en su única posición. Tenga en cuenta
que por convención usamos cursiva para los símbolos, y negrita para su correspondiente
expresión regular.
INDUCCIÓN: Hay cuatro partes que constituyen la inducción, mediante la cual las
expresiones regulares más grandes se construyen a partir de las más pequeñas. Suponga
que r y s son expresiones regulares que denotan a los lenguajes L(r) y L(s),
respectivamente.
1. (r)|(s) es una expresión regular que denota el lenguaje L(r) ∪ L(s).
2. (r)(s) es una expresión regular que denota el lenguaje L(r)L(s).
3. (r)* es una expresión regular que denota a (L(r))*.
4. (r) es una expresión regular que denota a L(r). Esta última regla dice que
podemos agregar pares adicionales de paréntesis alrededor de las
expresiones, sin cambiar el lenguaje que denotan.
Según su definición, las expresiones regulares a menudo contienen pares innecesarios de
paréntesis. Tal vez sea necesario eliminar ciertos pares de paréntesis, si adoptamos las
siguientes convenciones:
a) El operador unario * tiene la precedencia más alta y es asociativo a la izquierda.
b) La concatenación tiene la segunda precedencia más alta y es asociativa a la
izquierda.
c) | tiene la precedencia más baja y es asociativo a la izquierda.
3.3.4 Definiciones regulares
Lo más conveniente es poner nombres a ciertas expresiones regulares, utilizarlos en
expresiones subsiguientes, como si los nombres fueran símbolos por sí mismos. Si Σ es
un alfabeto de símbolos básicos, entonces una definición regular es una secuencia de
definiciones de la forma:
En donde decimos:
1. Cada di es un nuevo símbolo, que no está en Σ y no es el mismo que cualquier
otro d.
2. Cada ri es una expresión regular sobre el alfabeto Σ ∪ {d1, d2, …, di−1}.
3.3.5 Extensiones de las expresiones regulares
A partir de que Kleene, introduce las expresiones regulares con los operadores básicos
para la Unión, la concatenación y la cerradura de Kleene en 1950, agregado muchas
extensiones a las expresiones regulares para mejorar su habilidad al especificar los
patrones de cadenas.
Una o más instancias. El operador unario postfijo + representa la cerradura
positivo de una expresión regular y su lenguaje. Es decir, si r es una expresión
regular, entonces (r)+ denota el lenguaje (L(r))+. El operador + tiene la misma
precedencia y asociatividad que el operador *. Dos leyes algebraicas útiles, r* = r+|
∈ y r+ = rr * =r *r relacionan la cerradura de Kleene y la cerradura positiva.
Cero o una instancia. El operador unario postfijo ? significa “cero o una
ocurrencia”. Es decir, r? es equivalente a r|∈, o dicho de otra forma, L(r?) = L(r) ∪ {
∈}. El operador ? tiene la misma precedencia y asociatividad que * y +.
Clases de caracteres. Una expresión regular a1|a2|· · ·|an, en donde las ais son
cada una símbolos del alfabeto, puede sustituirse mediante la abreviación
[a1a2···an ]. Lo que es más importante, cuando a1, a2, …, an forman una
secuencia lógica, por ejemplo, letras mayúsculas, minúsculas o dígitos
consecutivos, podemos sustituirlos por a1-an; es decir, sólo la primera y última
separadas por un guión corto. Así, [abc] es la abreviación para a|b|c, y [a-z] lo es
para a|b|· · ·|z
3.4 Reconocimiento de tokens
Ahora analizaremos cómo tomar todos los patrones para todos los tokens necesarios y
construir una pieza de código para examinar la cadena de entrada y hallar un prefijo que
sea un lexema que coincida con 1 de sus patrones.
Esta sintaxis es similar a la del lenguaje Pascal porque then aparece en forma explícita
después de las condiciones. Para oprel, usamos los operadores de comparación de
lenguajes como Pascal o SQL, en donde = es “es igual a” y <> es “no es igual a”, ya que
presenta una estructura interesante de lexemas.
Las terminales de la gramática, que son if, then, else, oprel, id y numero, son los nombres
de tokens en lo que al analizador léxico respecta. Los patrones para estos tokens se
describen mediante el uso de definiciones regulares.
El analizador léxico reconocerá las palabras clave if, then y else, así como los lexemas
que coinciden con los patrones para oprel, id y numero. Para simplificar las cosas, vamos
a hacer la suposición común de que las palabras clave también son palabras reservadas;
es decir, no son identificadores, aun cuando sus lexemas coinciden con el patrón para
identificadores.
3.4.1 Diagramas de transición de estados
Los diagramas de transición de estados tienen una colección de nodos o círculos,
llamados estados. Cada estado representa una condición que podría ocurrir durante el
proceso de explorar la entrada, buscando un lexema que coincida con uno de varios
patrones. Podemos considerar un estado como un resumen de todo lo que debemos
saber acerca de los caracteres que hemos visto entre el apuntador inicioLexema y el
apuntador avance.
Las líneas se dirigen de un estado a otro del diagrama de transición de estados. Cada
línea se etiqueta mediante un símbolo o conjunto de símbolos. Si nos encontramos en
cierto estado s, y el siguiente símbolo de entrada es a, buscamos una línea que salga del
estado s y esté etiquetado por a.Si encontramos dicha línea, avanzamos el apuntador
avance y entramos al estado del diagrama de transición de estados al que nos lleva esa
línea. Asumiremos que todos nuestros diagramas de transición de estados son
deterministas, lo que significa que nunca hay más de una línea que sale de un estado
dado, con un símbolo dado de entre sus etiquetas.
Se dice que ciertos estados son de aceptación, o finales.
Además, si es necesario retroceder el apuntador avance una posición.
Un estado se designa como el estado inicial.
Hay dos formas en las que podemos manejar las palabras reservadas que parecen
identificadores:
1. Instalar las palabras reservadas en la tabla de símbolos desde el principio. Un
campo de la entrada en la tabla de símbolos indica que estas cadenas nunca
serán identificadores ordinarios, y nos dice qué token representan.
2. Crear diagramas de transición de estados separados para cada palabra clave; en
la figura 3.15 se muestra un ejemplo para la palabra clave then. Observe que
dicho diagrama de transición de estado consiste en estados que representan la
situación después de ver cada letra sucesiva de la palabra clave, seguida de una
prueba para un “no letra ni dígito”, es decir, cualquier carácter que no pueda ser la
continuación de un identificador.
BIBLIOGRAFÍA
Hopcroft, J. E., Motwani, R., & Ullman, J. D. (2007). Introducción a la Teoría de
autómatas, lenguajes y computación. Madrid: PEARSON EDUCACIÓN S.A.
AHO, A. V. (2008). COMPILADORES. PRINCIPIOS, TÉCNICAS Y
HERRAMIENTAS. Segunda edición. México: PEARS ON EDUCACIÓN,.