Introducción (1) flex ! El análisis léxico es una de las primeras fases de la etapa de análisis. ! En la construcción de analizadores léxico se pueden seguir dos enfoques: " Usar un lenguaje de programación como C y programar el analizador léxico. " Usar una herramienta como lex. ! La principal ventaja que ofrece el uso de lex es que facilita y acorta el desarrollo. Además como el fuente lex para un analizador léxico es más corto que el fuente del programa C, los errores, de producirse, son más fáciles de localizar. ! El analizador léxico lee los caracteres individuales del lenguaje fuente y los agrupa en entidades básicas llamadas tokenes. ! Las instancias particulares de tokenes se llaman lexemas. ! Para describir el formato de los lexemas que se corresponde con un token particular se puede usar una especificación informal, pero es más conveniente usar expresiones regulares. C é s ar Ig n a cio García O s orio Á r e a de L e n g u ajes y Sistem a s Inf o r m átic o s Universidad de Burg o s E n ero, 1999 © César Ignacio garcía Osorio. Universidad de Burgos. Introducción (2) El fuente lex ! lex es un generador de programas C que realiza un procesado léxico de tiras de caracteres ! El fuente lex se compone de sucesiones de expresiones regulares ! Genera un autómata finito determinista que es interpretado a la hora de analizar la entrada ! El intérprete del autómata dirige el flujo y ejecuta las acciones como switch de C {definiciones} %% {reglas} %% {rutinas de usuario} ! La complejidad esta fijada por el tamaño del autómata a traducir Expresiones regulares Acciones %% [a-z][a-z0-9]* \n %% {num_ident++;ECHO;} printf("\n%d ", ++num_lines); flex 2 ! Las definiciones y rutinas de usuario no aparecen en la mayoría de los casos ! El segundo %% es opcional ! El mínimo programa lex sería por tanto: %% que se traduce por un programa que copia la entrada en la salida Fuente lex Entrada yylex() Salida lex.yy.c © César Ignacio garcía Osorio. Universidad de Burgos. flex 3 © César Ignacio garcía Osorio. Universidad de Burgos. flex 4 Unos aperitivos (1) Unos aperitivos (2) ! Complie los siguientes ejemplos, usando las siguientes ordenes %{ " lex eje_n.l " gcc lex.yy.c -o eje_n -lfl " ejen /* * Un ejemplo sencillo */ %} %% %% %{ [\t ]+ /* Un ejemplo un poco mas ambicioso */ (soy|es|somos|son|sois) | int num_id=0; (fui|fuiste|fue|fuimos|fueron) | int num_lines=0; tienes | %} tienen | %% tengo [a-z][a-zA-Z0-9]* \n /* ignorar blancos */ {printf(“%s: es verbo\n”, yytext);} [a-zA-Z]+ num_id++;ECHO; {num_lines++; .|\n printf(“%d(%d)”,num_lines, num_id); %% } main(){ {printf(“%s: no verbo\n”, yytext);} {ECHO; /* accion por defecto */ yylex(); © César Ignacio garcía Osorio. Universidad de Burgos. flex 5 Expresiones regulares (1) flex 6 Expresiones regulares (2) ! Una expresión regular indica un conjunto de palabras. Puede contener texto y operadores, representados por caracteres especiales. ! Las letras y dígitos del alfabeto son texto de las expresiones regulares. #Operadores: los siguientes caracteres son operadores que componen el resto del alfabeto del metalenguaje lex " Cuando quiere incluirse alguno de ellos en la parte de texto de una expresión regular, se utilizan los caractes de escape “ y \: “xyz++” = xyz”++” = xyz\+\+ xa\ j (para indicar un espacio entre la a y la j) ahora\ncuando\t\\ (para indicar el final de línea, un tabulador y la aparición del backslash) #Carácter arbitrario: El operador “.” o el operador “,” representan cualquier carácter excepto el LF (line feed, salto de línea) " Es posible especificar caracteres arbitrarios utilizando el escape octal, aunque no es transportable #Elementos opcionales: El operador “?” indica un elemento opcional en una expresión regular. #Expresiones repetidas: Las repeticiones de clases o expresiones se indican por los operadores: “*” (0 o mas) y “+” (1 o mas) #Alternancia y agrupación: El operador “|” indica alternancia, y el operador “( )” agrupación #Sensibilidad al contexto: lex reconoce cierta sensibilidad al contexto por medio de los operadores “^”, “$”, “/” ^ cuando es el primer carácter de una expresión indica que la misma se aplicará sólo a principio de línea o de fichero. $ cuando es el último carácter de una expresión índica que la misma se aplica a final de línea. / indica contexto a la derecha para una expresión #Clases de caracteres: Conjuntos de caracteres que pueden aparecer en una expresión regular de forma alternativa. El operador alternativa es []. " Dentro de [] hay tres caracteres de significado especial: - este operador específica rangos (si va el primero forma #Repeticiones y definiciones: El operador “{ }” indica: parte de la clase) ^ si aparece, debe ser el primero, indica una complementación \ escape habitual © César Ignacio garcía Osorio. Universidad de Burgos. ©} César Ignacio garcía Osorio. Universidad de Burgos. a expansión de una definición cuando encierran el nombre de una tira predefinida b repetición de una expresión regular cuando encierran un rango numérico flex 7 © César Ignacio garcía Osorio. Universidad de Burgos. flex 8 Expresiones regulares (3) Expresiones regulares (4) #Precedencia de los operadores: Todos los operadores en lex obedecen a la sigueinte ordenación (de mayor a menor precedencia) * ? + %% [\n\t] ; -?(([0-9]+)|( [0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) {printf(“number\n”);} concatenación reperición {} $ ^ | / < > . ECHO; %% main() ! Ejemplos de expresiones regulares: { " Dígitos: [0-9] " Enteros: [0-9]* " Enteros con al menos un dígito: [0-9]+ " Enteros con un signo opcional: -?[0-9]+ " Número decimales: [0-9]*\.[0-9]+ " Decimales o enteros: ([0-9]+)|( [0-9]*\.[0-9]+) yylex(); } ! Más ejemplos de expresiones regulares: " Comentario de un fichero de scriptDígitos: #.* " Cadena de caracteres entrecomillada: \”[^”\n]*[”\n] Ojo con esta otra: \”.*\” y con esta otra: \”[^”]*\” " Decimales o enteros con signo: -?(([0-9]+)|( [0-9]*\.[0-9]+)) " Exponente: [eE][-+]?[0-9]+ " Número en coma flotante: ?(([0-9]+)|( [0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) © César Ignacio garcía Osorio. Universidad de Burgos. flex 9 Acciones en lex (1) © César Ignacio garcía Osorio. Universidad de Burgos. flex 10 Acciones en lex (2) ! Asociada a cada patrón hay una acción. ! La acción por defecto es copiar la entrada en la salida. ! Para no producir ninguna salida es preciso plantear expresiones que se ajusten a todas las tiras de entrada y absorber la salida. ! La acción nula se representa por “;” ! Cuando varias expresiones llevan asociada la misma acción puede emplearse “|” para unir las líneas. ! Es posible acceder a distintas variables, funciones y macros en la especificación de las acciones: yytext variable que almacena la secuencia de unput(c) devuelve el carácter c (que no puede ser EOF) al stream de entrada para su posterior input() devuelve el siguiente carácter de la entrada output(c) escribe el carácter en la salida ! Otras funciones en lex.yy.c y libl.a son: yywrap() se llama cuando se alcanza el final de fichero. Si devuelve 1, lex termina. Si devuelve un 0 sigue el proceso. caracteres que se ajusta a la expresión regular yyleng ECHO yymore() variable que almacena la longitud de yytext macro para presentar en stdout el contenido de yytext indica que la siguiente palabra reconocida debe añadirse al final de la actual (no elimina la “porción” ya ajustada) yyless(n) macro que indica que no todos los caracteres reconocidos se desean en el momento actual, solo n. El resto se retornan al stream de entrada y es como si no se hubieran procesado y por tanto se volverán a “escanear” © César Ignacio garcía Osorio. Universidad de Burgos. flex 11 © César Ignacio garcía Osorio. Universidad de Burgos. flex 12 Sección de definiciones (1) Reglas ambiguas ! Cuando más de una expresión regular sirve para describir una palabra, lex elige la regla de acuerdo con el siguiente criterio: $ Se prefiere la regla con cuyo patrón concuerde el mayor número de caracteres posible % Entre las reglas que ajustan el mismo número de caracteres, la escrita en primer lugar en el fichero fuente. ! Debido al criterio $ de resolución de ambigüedades, expresiones de la forma: ‘.*’, pueden conducir a resultados no deseados. © César Ignacio garcía Osorio. Universidad de Burgos. flex 13 ! El usuario necesita definir las variables que introduce en las porciones de programa C que forman la s acciones. ! Esto puede hacerse en la sección de definiciones o de reglas de acuerdo con los criterios: " Cualquier línea que no forma parte de una regla o acción y comienza por blancos o tabulador se copia en el programa lex.yy.c: & Si está antes del primer %% será una declaración o frase externa a cualquier función. & Si aparece inmediatamente después del primer %% se incluye como parte de yylex(). Debe ir antes de toda regla. " Cualquier cosa que aparezca entre líneas que contienen sólo %{ y %} se copia tal cual con las mismas consideraciones que antes. " Cualquier cosa situada detrás del segundo %% se copia tal cual al final de lex.yy.c " Las definiciones de lex propiamente dichas deben ir antes del primer %% y comenzar en la primera columna. © César Ignacio garcía Osorio. Universidad de Burgos. flex Sección de definiciones (2) Sensibilidad al contexto por la izquierda (1) ! Las definiciones sirven para definir símbolos que representan expresiones y que se utilizan a menudo en las reglas. ! Formato de las definiciones: ! Una extensión de lex similar a la sensibilidad por la derecha no es útil en la mayoría de los casos, por ejemplo un contexto a la izquierda muy alejado del carácter actual sería problemático. ! La solución se encuentra en el uso de banderas: " Manejadas por el usuario " Manejadas por lex ! A las banderas manejadas por lex se las denomina condiciones de arranque. nombre separador traducción ! El nombre debe comenzar en la primera columna. ! El separador puede ser un espacio o un tabulador. ! Además de las definiciones pueden aparecer otros comandos: selección del lenguaje, tabla de caracteres, lista de condiciones de arranque, ajuste de los tamaños por defecto de lex, ... 14 $ Se definen con la directiva %s (%x) en la sección de definiciones: & %s nom1 nom2 ... % Se activan dentro de una acción por medio de la directiva BEGIN: & BEGIN nom1; & BEGIN 0; ' Se incluyen como activadoras de reglas con los operadores < > a principio de una expresión: & <nom1, nom2>[a-z] acción; © César Ignacio garcía Osorio. Universidad de Burgos. flex 15 © César Ignacio garcía Osorio. Universidad de Burgos. flex 16 Sensibilidad al contexto por la izquierda (2) Juego de caracteres definidos por el usuario ! En flex (no en así en lex) hay dos primitivas para declarar los nombres de las condiciones de arranque: %s, %x. ! Con %s se declaran condiciones de arranque inclusivas. ! Con %x se declaran condiciones de arranque exclusivas. ! Si la condiciones de arranque es inclusiva, las reglas sin condición de arranque están también activas. ! Si la condiciones de arranque es exclusiva, solo se tienen en cuenta las reglas cualificadas con la condición de arranque activa. ! Un conjunto de reglas que compartan la misma condición de arranque describen un analizador léxico independiente de cualquier otra de las reglas del fuente flex. ! Aspectos avanzados de las condiciones de inicio: " YY_START (en lex se usa YYSTATE) ! Puede cambiarse la asignación numérica de un carácter a voluntad con la directiva %T ! Las limitaciones son: " No puede asociarse el 0 a ningún carácter. " Ningún carácter puede asignarse a un número mayor que el número de caracteres del juego hardware de la máquina. ! Formato: %T entero tira %T ! Ejemplo: %T 1 Aa 2 Bb 3 Cc . . 26 Zz " %option stack & void yy_push_state(int new_state) & void yy_pop_state() & int yy_top_state() %T © César Ignacio garcía Osorio. Universidad de Burgos. flex 17 Resumen del formato fuente de lex (1) 18 ! Reglas ! Responden al formato general: <expresión regular> blanco o tabulador <acción> ! Las expresiones regulares utilizan el siguiente conjunto de operadores: ! Definiciones "x tira de caracteres " "x" tira de caracteres aunque x sea un operador " \x carácter x aunque sea operador " [xy] carácter x ó y " [x-z] caracteres entre x y z del juego de caracteres " [^x] cualquier carácter salvo los que aparecen en x ". cualquier carácter salvo \n " ^x cualquier x al principio de línea " <y>x cualquier x, dada la condición de activación y " x$ cualquier x al final de línea o fichero " x? carácter x opcional " x* cero o más concatenaciones de x " x+ una o más concatenaciones de x " x|y expresión x ó y " (x) expresión x " x/y expresión x sólo si va seguida de y " {x} traducción de la definición x " x{m,n} de m a n concatenaciones de x " Definiciones de \lex: & nombre blanco traducción " Fragmentos de código: & blanco código " Fragmentos de código: %{ código %} " Condiciones de activación: & %S nom1 nom2 ... " Tablas de caracteres: número blanco tira %T " Cambios de dimensión de conjuntos internos de lex: & %carácter número & donde, número es un número decimal que representa una dimensión y carácter es un carácter clave a elegir entre los siguientes: p: posición, n: estados, e: nodos de árbol, a: transiciones, k: clases de caracteres empaquetados, o: tamaño conjunto de salida. © César Ignacio garcía Osorio. Universidad de Burgos. flex Resumen del formato fuente de lex (2) {definiciones} %% {reglas} %% {rutinas} & %T © César Ignacio garcía Osorio. Universidad de Burgos. flex 19 © César Ignacio garcía Osorio. Universidad de Burgos. flex 20 Ejemplos y ejercicios Un ejemplo ! El programa mínimo flex, es el siguiente: %% es decir, una única línea con dos signos de porcentaje. Compilarlo y echa un vistazo al programa C generado. © César Ignacio garcía Osorio. Universidad de Burgos. Otro ejemplo 22 Ejercicios ! Un programa para contar palabras: %{ unsigned charCount = 0, wordCount = 0, lineCount = 0; %} word [^ \t\n]+ eol \n %% {word} {wordCount++; charCount+=yyleng; } {eol} { charCount++; lineCount++; } . charCount++; %% main() { yylex(); printf(“%d %d %d\n”, lineCount, wordCount, charCount); } ! El programa anterior lee de la entrada estándar. Si se modifica la función main se puede conseguir que tome como argumentos el nombre del fichero del que se desean saber el número de palabras main(int argc, char **argv) { if(argc > 1) { FILE *file; file=fopen(argv[1], “r”); if(!file) { fprintf(stderr, “no se puede abrir %s\n”, argv[1]); exit(1); } yyin = file; } yylex(); printf(“%d %d %d\n”, lineCount, wordCount, charCount); } © César Ignacio garcía Osorio. Universidad de Burgos. flex flex 23 ! 1.- Construir un filtro que transforme todas las letras mayúsculas en minúsculas. ! 2.- Construir un programa que toma como argumento un nombre de fichero y muestra su contenido por pantalla anteponiendo a cada línea su número, también debe poder utilizarse como un filtro (como cat –n). ! 3.- Construir un programa que como cat –v permita mostrar de algún modo los caracteres no imprimibles, por ejemplo mostrando su número ASCII entre ángulos. ! 4.- Construir un programa al que se le pasa como argumento el nombre de un fichero. En el fichero entre van a aparecer números en distintas bases. La base en la que deben interpretarse los números se indica al final de estos, pudiendo utilizarse varios métodos tal como se indica a continuación: " 1011b " 172o " 987d " 6A0fCx 1011B 172O 987D 6A0fCX 1011(2) 172(8) 987(10) 6A0fC(16) en en en en binario octal decimal hexadecimal el programa ha de presentarlos en decimal y sin el indicador de base. © César Ignacio garcía Osorio. Universidad de Burgos. flex 24 Mas ejercicios ! 5.- Escribir un filtro que aplicado a la hora que devuelve el programa date, nos de los buenos días (7 a 15), las buenas tardes (15 a 20) o las buenas noches. Además nos ha de dar la hora en formato texto tal como se muestra en el ejmplo. Analizadores léxicos date | DEMO Buenas tardes, son las tres y cuarto. ! 6.- Hacer un pequeño “eliza”, he aquí algunas sugerencias de palabras clave a buscar en la entrada, posibles respuestas asociadas. © César Ignacio garcía Osorio. Universidad de Burgos. flex Algunas cuestiones sobre implantación 25 Manejo de buffers (2) Manejo de buffers (1) Pareja de buffers I ! Existen tres métodos generales de implantación de un analizador léxico: ! Se utiliza un buffer dividido en dos mitades de N caracteres cada una. Por lo general, N es el número de caracteres en un bloque de disco, por ejemplo, 1024 ó 4096. " Utilizar un generador de analizadores léxicos " Escribir el analizador léxico en un lenguaje convencional de programación de sistemas " Escribir el analizador léxico en lenguaje ensamblador E ! Estos enfoques tienen un orden de dificultad creciente, lamentablemente los enfoques más difíciles consiguen analizadores léxicos más rápidos. ! Como el analizador léxico es la única fase que lee el programa carácter a carácter su velocidad es un problema en el diseño de compiladores. ! Además en muchos lenguajes el analizador léxico necesita preanalizar varios caracteres, antes de poder anunciar una concordancia. Los caracteres preanalizados se tienen que devolver después a la entrada. Como se puede consumir mucho tiempo moviendo caracteres, se han desarrollado técnicas especializadas en el manejo de buffers para reducir el número de operaciones necesarias para procesar un carácter de entrada. © César Ignacio garcía Osorio. Universidad de Burgos. flex 27 = M * C * * 2 eof delantero comienzo_lexema ! Se leen N caracteres de entrada en cada mitad del buffer con una orden de lectura de sistema, en vez de invocar una instrucción de lectura para cada carácter de entrada. Si quedan menos de N caracteres en la entrada, entonces se lee un carácter especial eof en el buffer después de los caracteres de entrada. ! Se mantienen dos apuntadores al buffer de entrada. La cadena de caracteres entre los dos apuntadores es el lexema en curso. Al principio, los dos apuntadores apuntan al primer carácter del próximo lexema que hay que encontrar. ! El apuntador delantero, examina hacia delante hasta encontrar una concordancia con un patrón. Una vez determinado el siguiente lexema, el apuntador delantero se coloca en el carácter de su extremo derecho. © César Ignacio garcía Osorio. Universidad de Burgos. flex 28 Manejo de buffers (3) Manejo de buffers (4) Pareja de buffers II Centinelas ! Después de haber procesado el lexema, ambos apuntadores se colocan en el carácter situado inmediatamente después del lexema. ! Cuando el apuntador delantero esta apunto de sobrepasar por la marca intermedia del buffer, se llena la mitad derecha con N nuevos caracteres de entrada. ! Cuando el apuntador delantero está a punto de sobrepasar el extremo derecho del buffer, se llena la mitad izquierda con N nuevos caracteres de entrada y el apuntador delantero se regresa al principio del buffer if delantero está al final de la primera mitad then begin recargar la segunda mitad; delantero := delantero + 1; end else if delantero está al final de la segunda mitad then begin recargar la primera mitad; pasar delantero al principio de la primera mitad end else delantero := delantero + 1; ! Este esquema de manejo de buffers casi siempre funciona muy bien, pero limita la cantidad de caracteres de preanálisis, y esto puede imposibilitar el reconocimiento de los componentes léxicos cuando la distancia recorrida por el apuntador delantero sea mayor que la longitud del buffer. ! Si de usa el algoritmo tal como se ha expuesto, se necesitan dos comparaciones para cada avance del apuntador delantero. Se pueden reducir estas dos pruebas a una si se amplía cada mitad del buffer para admitir un carácter centinela al final. ! El centinela es una carácter especial. Una elección natural es el eof. © César Ignacio garcía Osorio. Universidad de Burgos. flex 29 Implantación de un diagrama de transiciones (1) ! A partir del diagrama de transiciones se puede construir “a mano” un programa analizador léxico de un modo sistemático. ! En general puede haber varios diagramas de transiciones, cada uno de los cuales especifique un grupo de componentes léxicos. Si surge un fallo mientras se esta siguiendo un diagrama de transiciones, se debe retroceder el apuntador delantero hasta donde estaba en el estado inicial de dicho diagrama, y activar el siguiente diagrama de transiciones (igualar el apuntador delantero al valor del apuntador de inicio de lexema). Si el fallo surge en todos los diagramas de transiciones, es que se ha detectado un error léxico y se invoca una rutina de recuperación de errores. E = * eof C * * 2 eof M delantero comienzo_lexema ! El siguiente código realiza sólo una comparación en la mayoría de las ocasiones Delantero := delantero + 1; if delantero = eof then begin if delantero esta al final de la primera mitad then begin recargar la segunda mitad; delantero := delantero + 1; end else if delantero al final de la segunda mitad then begin recargar la primera mitad; pasar delantero al principio de la primera mitad end else /* eof dentro de bufer*/ terminar la entrada ©end César Ignacio garcía Osorio. Universidad de Burgos. flex 30 Implantación de un diagrama de transiciones (2) < inicio 0 = 1 > 2 devuelve(oprel, MEI) 3 devuelve(oprel, DIF) otro * devuelve(oprel, MEN) 4 = devuelve(oprel, IGU) 5 > = 6 otro inicio 9 letra * 11 * devuelve(obten_complex(), instala_id()) dígito dígito 12 13 . devuelve(oprel, MAY) 8 otro 10 devuelve(oprel, MAI) 7 letra o dígito inicio dígito dígito dígito 14 15 E 16 +o- 17 dígito inicio dígito 25 flex 31 26 otro 18 otro 19 dígito E dígito © César Ignacio garcía Osorio. Universidad de Burgos. eof delim * 27 inicio delim 28 © César Ignacio garcía Osorio. Universidad de Burgos. 29 otro flex * 30 32 Implantación de un diagrama de transiciones (3) ! Una secuencia de diagramas de transiciones se puede convertir en un programa que busque los componentes léxicos especificados por los diagramas. ! A cada estado del diagrama le corresponde un segmento de código. Si hay aristas que salen de un estado, entonces su código lee un carácter y selecciona una arista para seguir, si es posible. Si hay una arista etiquetada con el carácter leído, o etiquetada con una clase de caracteres que contenga el carácter leído, entonces el control se transfiere al código del estado apuntado por esa arista. ! Si no hay tal arista y el estado en curso de ejecución no es el que indica que se ha encontrado un componente léxico, entonces se llama a la rutina fallo() para hacer retroceder el apuntador delantero a la posición del apuntador al comienzo e iniciar la búsqueda del componente léxico especificado por el siguiente diagrama de transiciones. ! Si no hay que probar más diagramas de transiciones, fallo() llama a una rutina de recuperación de errores. Implantación de un diagrama de transiciones (4) int estado = 0, inicio = 0; int valor_lexico; int fallo(){ delantero=inicio_lexema; switch(inicio){ case 0: inicio=9; break; case 9: inicio=12; break; case 12: inicio=20; break; case 20: inicio=25; break; case 25: recupera(); break; default: /* error */ } } ! Para devolver los componentes léxicos se usa una variable global valor_lexico. © César Ignacio garcía Osorio. Universidad de Burgos. flex 33 Implantación de un diagrama de transiciones (5) complex sigte_complex(){ while(1){ switch(estado){ case 0: c = sigtecar(); if(c==blanco || c==tab || c==newline){ estado = 0; inicio_lexema++; } else if (c == `<´) estado = 1; else if (c == `=´) estado = 5; else if (c == `>´) estado = 6; else estado = fallo(); break; case 9: c = sigtecar(); if (isletter(c)) estado = 10; else estado = fallo(); break; case 10: c = sigtecar(); if (isletter(c)) estado = 10; else if (isdigit(c)) estado = 10; else estado = 11; break; case 11: regresa(1); instala_id; return ( obten_complex() ); . . . . . . /* aquí los casos 11 al 24 */ . . . case 25: c = sigtecar(); if (isdigit(c)) estado = 26; else estado = fallo(); break; case 26: c = sigtecar(); if (isdigit(c)) estado = 26; else estado = 27; break; case 27: regresa(1); instala_num(); return( NUM );}}} © César Ignacio garcía Osorio. Universidad de Burgos. flex © César Ignacio garcía Osorio. Universidad de Burgos. flex 34 Diseño de un generador de analizadores léxicos (1) ! Supóngase que se tiene una especificación de un analizador léxico de la forma: p1 p2 ... pn {acción1} {acción2} {acciónn} pi es una expresión regular accióni es un fragmento de programa que debe ejecutarse siempre que se encuentre en la entrada un lexema que concuerde con pi ! El problema es construir un reconocedor que busque lexemas en el buffer de la entrada. Si concuerda más de un patrón, el reconocedor elegirá el lexema más largo que haya concordado. Si hay dos o más patrones que concuerden con el lexema más largo, se elige el primer patrón que haya concordado de la lista. ! Un método es construir la tabla de transiciones de un autómata finito no determinista N para el patrón compuesto p1|p2| ... | pn. Esto se puede hacer creando primero un AFND N(pi) para cada patrón pi utilizando el algoritmo de Thompson, añadiendo después un nuevo estado de inicio s0, y por último enlazando s0 al estado de inicio de cada N(pi) con una transición ε. ε s0 N(p1) ε ε 35 N(p2) ... N(pn) © César Ignacio garcía Osorio. Universidad de Burgos. flex 36 Diseño de un generador de analizadores léxicos (2) especificación de LEX simulador del AFND Compilador de LEX tabla de transiciones ! En el AFND combinado obtenido, hay un estado de aceptación para cada patrón pi. Cuando se simula el AFND utilizando el algoritmo anterior, se construye la secuencia de conjuntos de estados donde puede estar el AFND combinado después de leer cada carácter de entrada. Incluso si se encuentra un conjunto de estado que contenga un estado de aceptación, para encontrar la concordancia más larga se debe seguir simulando el AFND hasta alcanzar terminación, es decir, un conjunto de estados desde el que no hay transiciones con el símbolo de entrada en curso. flex buffer de entrada léxema ! Ahora podría simular el AFND con un algoritmo como el siguiente: S ← cerr-ε({s0 }); c ← sgtecar(); while c ≠ e.o.f do begin S ← cerr-ε(mueve(S,c)); c ← sgtecar(); end if S∩F≠∅ then return “SI” else return “NO” © César Ignacio garcía Osorio. Universidad de Burgos. Diseño de un generador de analizadores léxicos (3) 37 Métodos para comprensión de tablas (1) ! El proceso de análisis léxico ocupa una parte considerable del tiempo del compilador, puesto que es el único proceso que debe observar en la entrada un carácter a la vez. Por tanto, el analizado léxico debe minimizar el número de operaciones que realiza por cada carácter de entrada. Si se utiliza un AFD para ayudar a implantar el analizador léxico, es aconsejable una representación eficiente de la función de transición. ! Existen muchas formas de implantar la función de transición de un autómata finito: " Matriz bidimensional, indexada por estados y caracteres. Proporciona el acceso más rápido, pero puede ocupar demasiado espacio. " Lista enlazada para almacenar las transiciones de salida de cada estado, con una transición “por omisión” al final de la lista: es un esquema mas compacto, pero más lento ! Para encontrar la concordancia adecuada, se hacen dos modificaciones al algoritmo anterior. ! Primero, siempre que se añada un estado de aceptación al conjunto de estados en curso, se registran la posición en curso de entrada y el patrón pi correspondiente a este estado de aceptación. Si el conjunto de estados en curso ya contiene un estado de aceptación, entonces solo se registra el patrón que aparezca primero en la especificación de LEX. ! Segundo, se continúa haciendo transiciones hasta que se alcanza la terminación. En la terminación se retrocede el apuntador delantero a la posición en que ocurrió la última concordancia. El patrón que hizo dicha concordancia identifica al componente léxico encontrado, y el lexema emparejado es la cadena entre los apuntadores de inicio del lexema y los delanteros. (Además existe un patrón de error) © César Ignacio garcía Osorio. Universidad de Burgos. flex 38 Métodos para comprensión de tablas (2) ! Existe una representación que combina el acceso rápido de la representación por medio de matrices con la compacidad de las estructuras de listas. Se utiliza una estructura de datos que consta de tres matrices indexadas por números de estados. base s siguiente revisa a r l ! Se utiliza la matriz base para determinar la posición base de las entradas para cada estado almacenado en las matrices siguiente y revisa. function sigte_estado(s,a); if revisa[base[s]+a] =s then return siguiente[base[s ]+a] else return -1 ! Para iniciar la matriz siguiente se puede usar una estrategia de primero el que mejor ajuste. La siguiente fila a almacenar se “mueve” sobre siguiente sin que se produzca “colisión” © César Ignacio garcía Osorio. Universidad de Burgos. flex 39 © César Ignacio garcía Osorio. Universidad de Burgos. flex 40