El proceso del Análisis Léxico
El proceso de análisis léxico se refiere al trabajo que realiza el scanner con relación al proceso de compilación.
El scanner representa una interfaz entre el programa fuente y el analizador sintáctico o parser.
El scanner, a través del examen carácter por carácter del texto, separa el programa fuente en piezas llamadas tokens, los cuales representan los nombres de las variables, operadores, etiquetas, y todo lo que comprende el programa fuente.
El parser, usualmente genera un árbol de sintaxis del programa fuente como ha sido definido por una gramática.
Las hojas del árbol son símbolos terminales de la gramática. Son esos símbolos terminales o tokens los que el scanner extrae del código fuente y se los pasa al parser.
Es posible para el parser usar el conjunto de caracteres terminales del lenguaje como el conjunto de tokens, pero ya que los tokens pueden ser definidos en términos de gramáticas regulares más simples que en las gramáticas más complejas utilizadas por los parsers, es deseable usar scanners.
Usar solo parsers es costoso en términos de tiempo de ejecución y requerimientos de memoria, y la complejidad y el tiempo de ejecución puede reducirse con el uso de un scanner.
La separación entre análisis léxico (scanning) y análisis sintáctico (parsing) puede tener también otras ventajas.
El análisis léxico de caracteres generalmente es lento en los compiladores, y separándolo del componente de análisis semántico de la compilación, el énfasis particular puede darse para hacer más eficiente el proceso.
Un analizador de léxico tiene como función principal el tomar secuencias de caracteres o símbolos del alfabeto del lenguaje y ubicarlas dentro de categorías, conocidas como unidades de léxico.
Las unidades de léxico son empleadas por el analizador gramatical para determinar si lo escrito en el programa fuente es 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.
En la terminología empleada en la construcción de un analizador de léxico se encuentran los siguientes términos.
Patrón Representa la regla para que una secuencia de caracteres sea considerada cierta unidad de léxico.
Ejemplo: El patrón para un identificador de Pascal es: Una letra seguida por letras, dígitos o guiones (_)
Lexema
El valor actual de un conjunto de caracteres que satisfacen un patrón. Ejemplo: Este_es_1_ejemplo
Este es el lexema que satisface el patrón de un identificador
Token o Ficha
El valor asociado a una categoría o unidad de léxico.
Se representa como un número entero o una constante de un byte.
Ejemplo: el token de un identificador puede ser 1 ó id (si id fue definida como 1). Unidades de léxico
Categorías en que se clasifican las cadenas de caracteres válidos en un lenguaje.
Los caracteres válidos reciben el nombre de alfabeto.
Por ejemplo, el alfabeto de Pascal es:
A-Z, a-z, 0-9, _, =, :, ;, ,, , -, ', ", *, /, (, ), [, ], ., <, > y las unidades de léxico para pascal son: identificadores literales numéricas operadores aritméticos cadenas de caracteres separadores operadores relacionales operadores lógicos comentarios
Con respecto al lenguaje para controlar al ROBOT, tenemos que su alfabeto es:
n,o,r,t,e,s, ,u,i,c
y las unidades de léxico son:
órdenes
(norte, sur, este, oeste, inicio)
El rol del analizador de Léxico
Aunque el analizador de léxico es la primera etapa del proceso de compilación, no es quien lo inicia.
Pudiera considerarse que el analizador de léxico hace su procesamiento y envía sus resultados al analizador gramatical, como secuencialmente se aprecia en el proceso de compilación; no es así:
La compilación empieza con el analizador gramatical quien solicita un token para realizar su trabajo;
el analizador de léxico reune símbolos y envía el token correspondiente a la unidad de léxico que conformó al analizador gramatical y espera una nueva solicitud de token.
Como se aprecia en la figura siguiente, el analizador de léxico está supeditado por el analizador gramatical.
Durante estas etapas se tiene comunicación con la tabla de símbolos que concentra información de las entidades empleadas en el programa.
Descripción de Patrones Un patrón se puede describir:
1. Mediante una descripción informal, en donde se emplea el lenguaje natural para describir el comportamiento de la regla de léxico.
Por ejemplo: un número entero es una secuencia de uno o más dígitos del 0 al 9.
O un identificador es una letra seguida de letras, dígitos o guiones de subrayar.
computacionalmente aún no hay herramientas para construir sobre ellas analizadores de léxico.
2. Utilizando expresiones regulares.
Una expresión regular es una notación formal que utiliza operaciones sobre el alfabeto de un lenguaje.
Por ejemplo, se puede definir que un identificador es: {letra} ({letra} | {dígito} | {guión})*
que interpreta como un elemento del conjunto letra seguido de cero o mas veces (la cerradura Kleene, representada por el asterisco)
de una letra, dígito o guión (la selección representada por la barra vertical). Esta notación es formal y computacionalmente útil para construir analizadores de léxico empleando la herramienta LEX.
3. Utilizando autómatas finitos (diagramas de transición o diagramas sintácticos), que son representaciones gráficas de las relaciones entre conjuntos de símbolos (aristas) por medio de estados, a los cuales pueden llegarse o transitarse por ellos al encontrar un símbolo perteneciente a un conjunto. El siguiente diagrama puede ser la representación de un identificador:
La utilización del diagrama sirve para aclarar las posibilidades de acción en un patrón y puede manipularse computacionalmente.
Diagramas de Estado.
Ya que el scanner debe reconocer tokens, debemos buscar la posibilidad de describir los tokens a manera de reconocimiento y no de manera generativa.
La descripción de los tokens por medio de cómo pueden ser reconocidos (o aceptados) se hace en términos de un modelo matemático llamado un aceptor de estado finito.
En lo que resta de esta sección, describiremos un conjunto de tokens por medio de la especificación de un aceptor que reconocerá ese conjunto.
Cabe aclarar que las gramáticas regulares también pueden utilizarse para este propósito.
Un aceptor de estado finito o autómata finito puede considerarse como una máquina consistente de una cabeza de lectura y una caja de control de estado finito.
La máquina lee una cinta de carácter a la vez, de izquierda a derecha. Existe un número finito de estados que la máquina puede adoptar.
Cada vez que la máquina lee el siguiente carácter, ocurre en ella un cambio de estado.
Siempre que un aceptor de estado finito inicia la lectura de una cinta, éste se encuentra en cierto estado llamado estado inicial.
Algunos de los estados que el aceptor puede adoptar se llaman estados finales, y si el aceptor intenta leer más allá del final de la cinta mientras se encuentra en un estado final, la cadena que está en la cinta se dice que fue aceptada por el autómata finito.
En otras palabras, la cadena pertenece al lenguaje que es aceptado por el autómata finito.
Diagramas de Estado Finito.
Para representar gráficamente un aceptor de estado finito, también se utilizan los diagramas de estado finito o diagramas de transición, como el que se
muestra en la siguiente figura y que representa el aceptor para un número con al menos un dígito después del punto decimal.
Los nodos del diagrama de estado finito representan los estados del aceptor de estado finito.
En el diagrama anterior, los nodos se han etiquetado con los números 1, 2 y 3. Los arcos, que van de un estado otro, indican las transiciones de estado.
La flecha y la palabra "Inicio" indican cual de los estados es el estado inicial (en este caso el estado 1).
El estado etiquetado con el 3 se denomina estado final.
Generalmente los estados finales se representan por medio de dos círculos concéntricos, pero en nuestro caso, para facilitar su construcción, hemos utilizado un circulo con línea más gruesa.
Los símbolos sobre los arcos representan los caracteres que, al leerse, obligan al cambio de un estado a otro.
Un diagrama de transición es una representación gráfica donde se tiene un conjunto de estados, los cuales pueden ser:
iniciales finales intermedios
Los estados se relacionan entre sí con flechas con un nombre (el caracter o conjunto de caracteres que provoca la transición de un estado a otro).
Un estado final se representa con :
que también recibe el nombre de estado de aceptación.
Para construir un diagrama de transición se debe tener presente:
A cada estado debe llegarse con el mismo conjunto de caracteres en todas las ocasiones en que haya un transición.
Para llegar a un estado de aceptación debe existir una transición sobre el caracter que rompe el patrón de la unidad de léxico.
Cuando se construye un analizador de léxico utilizando diagramas de transición para la especificación de los patrones, se realiza un único diagrama que, a partir del estado 0, tiene diversas transiciones a cada uno de los patrones de las unidades de léxico que deba reconocer.
Cada patrón posee un caracter selector, que permite reconocer de manera única el patrón que deba aplicarse.
Por ejemplo, si queremos reconocer identificadores, comentarios apegados a las reglas del lenguaje C y el fin de archivo, podriamos contruir el siguiente diagrama:
Las descripciones informales de las unidades de léxico son: Identificador: Letra seguida de letra, dígitos o guiones Comentario: Empieza con /* y termina con */.
Entre estos pares puede haber cualquier símbolo
EOF: cuando se encuentra el caracter eof (fin de archivo)
Error: Cualquier símbolo que no cumpla con los patrones anteriores. Los caracteres selectores para cada patrón son:
Identificador: letra Comentario: / EOF: eof
Error: cualquier símbolo diferente a los anteriores.
Las descripciones informales de las unidades de léxico son: Identificador: Letra seguida de letra, dígitos o guiones Comentario: Empieza con /* y termina con */.
Entre estos pares puede haber cualquier símbolo
EOF: cuando se encuentra el caracter eof (fin de archivo)
Error: Cualquier símbolo que no cumpla con los patrones anteriores. Los caracteres selectores para cada patrón son:
Identificador: letra Comentario: / EOF: eof
Estado A-Z 0-9 _ / * eof otro 0 1 13 13 5 13 12 13 1 1 2 3 4 4 4 4 2 1 2 3 4 4 4 4 3 1 2 3 4 4 4 4 4 5 14 14 14 14 6 14 14 6 8 8 8 8 7 15 8 7 10 10 10 9 7 15 10 8 8 8 8 8 7 15 8 9 11 11 11 11 11 11 11 10 8 8 8 8 7 15 8 11 12 13 14 15
Los estados finales o de aceptación de las unidades de léxico quedan sin información en las columnas ya que no existe una transición al reconocer o aceptar el patrón.
Note que la designación de otro corresponde a los símbolos diferentes a los expresamente indicados y que para cada patrón es contextual.
Para poder realizar el análisis de léxico a partir de la información que contiene la tabla se cuenta con un mecanismo que parte del estado 0 y empieza a recorrer el diagrama por cada transición posible hasta llegar a un estado final.
El algoritmo del mecanismo se describe a continuación. Analizador_de_Lexico INICIO Estado=0 C=Inspecciona() Cont=0 Nuevo_Estado=TABLA[Estado, C]
MIENTRAS Nuevo_Estado no sea Estado_Final REPITE Avanza() C=Inspecciona() Estado=Nuevo_Estado Nuevo_Estado=TABLA[Estado, C] Cont=Cont+1
FREPITE
SI Cont es 0 ENTONCES Avanza()
FSI
Regresa Token (Estado_Nuevo) FIN Analizador_de_Lexico INICIO Estado=0 C=Inspecciona() Cont=0 Nuevo_Estado=TABLA[Estado, C]
MIENTRAS Nuevo_Estado no sea Estado_Final REPITE Avanza() C=Inspecciona() Estado=Nuevo_Estado Nuevo_Estado=TABLA[Estado, C] Cont=Cont+1 FREPITE SI Cont es 0 ENTONCES Avanza() FSI
Regresa Token (Estado_Nuevo) FIN
Si no entiendes el diagrama, entonces en una hoja de papel realizas la 1, 2, 3…pruebas de escritorio CLARO..
La función Analizador_de_Léxico utiliza dos funicones que permiten consumir los símbolos del programa fuente: Inspecciona y Avanza. Las funciones consideran la existencia de un apuntador AP que direcciona al caracter próximo a leer. Inspecciona regresa el caracter apuntado por AP pero no lo incrementa. La función Avanza no regresa nada pero incrementa el valor de AP para estar en el siguiente símbolo a leer.