INTRODUCCIÓN

 

A continuación se describe una herramienta particular, llamada Lex, que ha sido ampliamente usada para especificar analizadores léxicos para una variedad de lenguajes. Se hará referencia a la herramienta como el compilador Lex, y a su especificación de entrada como el lenguaje Lex.

La discusión de una herramienta existente nos permitirá mostrar como la especificación de patrones usando expresiones regulares puede estar combinada con acciones, como por ejemplo, crear entradas en una tabla de símbolos, expandir macros, o incluso generar documentación automáticamente.

El programa Lex está diseñado para ser utilizado junto con el programa Yacc, que es un generador de analizadores sintácticos.

 

setstats1¿QUÉ ES LEX?

 

Lex es un generador del programa diseñado para el proceso léxico de flujos de entrada de carácter. Acepta un nivel alto y produce un programa en un idioma general que reconoce las expresiones regulares. Las expresiones regulares se especifican por los usuarios en la fuente de Lex.

El código Lex reconoce expresiones en un flujo de entrada y particiona las cadenas que emparejan las expresiones. Los límites entre las secciones de programa de cadenas, proporcionados por el usuario se ejecutan. La fuente de archivos Lex asocia las expresiones regulares y los fragmentos del programa. Cuando cada expresión aparece en la entrada al programa escrito por Lex, el fragmento correspondiente se ejecuta. 

Lex no es un lenguaje completo, sino un generador que representa un nuevo rasgo del lenguaje que puede agregarse a los lenguajes de programación diferentes, llamado ó `` lenguajes anfitrión'' así como los lenguajes de propósito generales pueden producir el código para correr en el hardware de la computadora diferente, Lex puede escribir el código en lenguajes anfitriones diferentes. El lenguaje del anfitrión se usa para el código del rendimiento generado por Lex y también para los fragmentos del programa agregados por el usuario.

También se proporcionan bibliotecas compatibles para los lenguajes anfitriones diferentes. Esto hace Lex adaptable a diferentes ambientes y a usuarios diferentes. Cada aplicación puede dirigirse a la combinación de hardware y lenguaje del anfitrión  apropiado a la tarea, los antecedentes del usuario, y las propiedades de aplicaciones locales. En la actualidad, el único lenguaje anfitrión es C, aunque Fortran ha estado disponible en el pasado. El propio Lex existe en UNIX, GCOS, y OS/370; pero el código generado por Lex puede tomarse en cualquier parte de los recopiladores apropiados que existen. 

3. ¿CÓMO SE COMPONE LEX?

Un programa Lex consta de tres secciones:

 

Ø       La primera 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.

 

Ø       La segunda sección son 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ón 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.

 

¿CÓMO SE COMPONE LEX?

Un programa Lex consta de tres secciones:

Ø       La primera 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.

Ø       La segunda sección son 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ón 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.

FUNCIÓN DE  LEX

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ón, 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.

 

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.

 

USO DE LEX

Lex se puede usar sólo para transformaciones sencillas, o por análisis o estadísticas buscando en un nivel léxico. Lex también se puede usar con un generador reconocedor para llevar a cabo la fase de análisis léxico; es especialmente fácil hacer que el lex y el yacc funcionen juntos.

Los programas lex reconocen sólo expresiones regulares; yacc escribe reconocedores que aceptan una amplia clase de gramáticas de texto libre, pero que requieren un analizador de nivel bajo para reconocer tokens de input. Por lo tanto, a menudo es conveniente una combinación del lex y del yacc.

Cuando se usa como un preprocesador para un generador, lex se usa para dividir el input, y el generador de reconocimiento asigna una estructura a las piezas resultantes. Los programas adicionales, escritos por otros generadores o a mano, se pueden añadir fácilmente a programas que han sido escritos por el lex. Los usuarios del yacc se darán cuenta de que el nombre yylex es el que el yacc da a su analizador léxico, de forma que el uso de este nombre por el lex simplifica el interface.

Lex genera un autómata finito partiendo de expresiones regulares del fuente. El autómata es interpretado, en vez de compilado, para ahorrar espacio. El resultado es todavía un analizador rápido. En particular, el tiempo que utiliza un programa lex para reconocer y dividir una cadena de input es proporcional a la longitud del input. El número de órdenes lex o la complejidad de las órdenes no es importante para determinar la velocidad, a no ser que las órdenes que incluyan contexto posterior requieran una cantidad importante de exploración. Lo que aumenta con el número y complejidad de las órdenes es el tamaño del autómata finito, y por lo tanto el tamaño del programa generado por el lex.

En el programa escrito por lex, los fragmentos del usuario (representando acciones que se van a llevar a cabo a medida que se encuentra cada expresión) se colectan como casos de un intercambio. El intérprete del autómata dirige el flujo de control. Se proporciona la oportunidad al usuario para insertar declaraciones o sentencias adicionales en la rutina que contiene las acciones, o para añadir subrutinas fuera de esta rutina de acción. El lex no está limitado a fuente que se puede interpretar sobre la base de un posible carácter.

Por ejemplo, si hay dos órdenes una que busca ab y la otra que busca abcdefg, y la cadena de caracteres del input es abcdefh, lex reconocerá ab y dejará el puntero del input justo delante de cd. Tal precaución es más costosa que el proceso de lenguajes más sencillos.

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.

 

FORMATO FUENTE DE LEX

El formato general de la fuente lex es:

{definiciones}

%%

{órdenes}

%%

{subrutinas del usuario}

donde las definiciones y las subrutinas del usuarios se omiten a menudo. El segundo %% es opcional, pero el primero se requiere para marcar el principio de las órdenes.

El programa lex mínimo absoluto es por lo tanto %% (sin definiciones, ni órdenes) lo cual se traduce en un programa que copia el input en el output sin variar.

En el formato del programa lex que se mostró anteriormente, las órdenes representan las decisiones de control del usuario. Estas forman una tabla en la cual la columna izquierda contiene expresiones regulares y la columna derecha contiene decisiones, fragmentos de programas que se ejecutarán cuando se reconozcan las expresiones.

Por lo tanto la siguiente orden individual puede aparecer: integer printf(“ localizada palabra reservada INT ”) ; Esto busca el literal “integer” en el input e imprime el mensaje: localizada palabra reservada INT siempre que aparezca en el texto de input. En este ejemplo la función de librería printf( ) se usa para imprimir la cadena de caracteres o literal.

El final de la expresión regular lex se indica por medio del primer carácter espacio en blanco o tabulador. Si la acción es simplemente una sola expresión C, se puede especificar en el lado derecho de la línea; si es compuesta u ocupa más de una línea, deberá incluirse entre llaves.

Como ejemplo un poco más útil, suponga que se desea cambiar un número de palabras de la ortografía Británica a la Americana. Las órdenes lex tales como colour printf(“color”); mechanise printf(“mechanize”); petrol printf(“gas”); será una forma de empezar.

Estas órdenes no son suficientes puesto que la palabra petroleum se convertirá en gaseum; una forma de proceder con tales problemas se describe en una sección posterior.

 

EXPRESIONES DE LEX

Una expresión especifica un conjunto de literales que se van a comparar. Esta contiene caracteres de texto (que coinciden con los caracteres correspondientes del literal que se está comparando) y caracteres operador (estos especifican repeticiones, selecciones, y otras características).

Las letras del alfabeto y los dígitos son siempre caracteres de texto. Los caracteres operador son: “ \ [ ] ^ - ? . * + | ( ) $ / { } % < >

Si cualquiera de estos caracteres se va a usar literalmente, es necesario incluirlos individualmente entre caracteres barra invertida ( \ ) o como un grupo dentro de comillas ( “ ).

El operador comillas ( “ ) indica que siempre que esté incluido dentro de un par de comillas se va a tomar como un carácter de texto. Por lo tanto xyz“++” coincide con el literal xyz++ cuando aparezca. Nótese que una parte del literal puede estar entre comillas. No produce ningún efecto y es innecesario poner entre comillas caracteres de texto normal; la expresión “xyz++” es la misma que la anterior. Por lo tanto poniendo entre comillas cada carácter no alfanumérico que se está usando como carácter de texto, no es necesario memorizar la lista anterior de caracteres operador.

Un carácter operador también se puede convertir en un carácter de texto poniéndole delante una barra invertida ( \ ) como en xyz\+\+ el cual, aunque menos legible, es otro equivalente de las expresiones anteriores. Este mecanismo también se puede usar para incluir un espacio en blanco dentro de una expresión; normalmente, según se explicaba anteriormente, los espacios en blanco y los tabuladores terminan una orden. Cualquier carácter en blanco que no esté contenido entre corchete tiene que ponerse entre comillas.

Se reconocen barios escapes C normales con la barra invertida ( \ ):

\ n  newline

\ t   tabulador

\ b  backspace

\ \   barra invertida

Puesto que el carácter newline es ilegal en una expresión, es necesario usar n; no se requiere dar escape al carácter tabulador y el backspace. Cada carácter excepto el espacio en blanco, el tabulador y el newline y la lista anterior es siempre un carácter de texto.

8. ESPECIFICACIÓNES DE LEX

ESPECIFICACIÓN DE CLASES DE CARACTERES.

Las clases de caracteres se pueden especificar usando corchetes: [y]. La construcción [ abc ] coincide con cualquier carácter, que pueda se una a, b, o c. Dentro de los corchetes, la mayoría de los significados de los operadores se ignoran. Sólo tres caracteres son especiales: éstos son la barra invertida ( \ ), el guión ( - ), y el signo de intercalación ( ^ ).

El carácter guión indica rangos, por ejemplo [a-z0-9<>_ ] indica la clase de carácter que contiene todos los caracteres en minúsculas, los dígitos, los ángulos y el subrayado. Los rangos se pueden especificar en cualquier orden. Usando el guión entre cualquier par de caracteres que ambos no sean letras mayúsculas, letras minúsculas, o dígitos, depende de la implementación y produce un mensaje de aviso. Si se desea incluir el guión en una clase de caracteres, éste deberá ser el primero o el último; por lo tanto [-+0-9 ] coincide con todos los dígitos y los signos más y menos.

En las clases de caracteres, el operador ( ^ ) debe aparecer como el primer carácter después del corchete izquierdo; esto indica que el literal resultante va a ser complementado con respecto al conjunto de caracteres del ordenador. Por lo tanto [ ^abc ] coincide con todos los caracteres excepto a, b, o c, incluyendo todos los caracteres especiales o de control; o [ ^a-zA-Z ] es cualquier carácter que no sea una letra.

El carácter barra invertida ( \ ) proporciona un mecanismo de escape dentro de los corchete de clases de caracteres, de forma que éstos se pueden introducir literalmente precediéndolos con este carácter.

ESPECIFICACIÓN DE UN CARÁCTER ARBITRARIO

Para hacer coincidir casi con cualquier carácter, el punto ( . ) designa la clase de todos los caracteres excepto un carácter newline. Hacer escape en octal es posible, aunque esto no es portable. Por ejemplo [ \ 40 - \ 176 ] coincide con todos los caracteres imprimibles del conjunto de caracteres ASCII, desde el octal 40 (espacio en blanco) hasta el octal 176 ( la tilde).

ESPECIFICAR EXPRESIONES OPCIONALES

El operador signo de interrogación ( ? ) indica un elemento opcional de una expresión. Por lo tanto ab?c coincide o con ac o con abc. Nótese que aquí el significado del signo de interrogación difiere de su significado en la shell.

ESPECIFICACIÓN DE EXPRESIONES REPETIDAS.

Las repeticiones de clases se indican con los operadores asterisco ( * ) y el signo más ( + ). Por ejemplo a* coincide con cualquier número de caracteres consecutivos, incluyendo cero; mientras que a+ coincide con una o más apariciones de a. Por ejemplo, [ a-z ]+ coincide con todos los literales de letras minúsculas, y [ A-Za-z ] [A-Za-z0-9 ]* coincide con todos los literales alfanuméricos con un carácter alfabético al principio; ésta es una expresión típica para reconocer identificadores en lenguajes informáticos.

 

ESPECIFICACIÓN DE ALTERNACIÓN Y DE AGRUPAMIENTO.

El operador barra vertical ( | ) indica alternación. Por ejemplo ( ab|cd ) coincide con ab o con cd. Nótese que los paréntesis se usan para agrupar, aunque éstos no son necesarios en el nivel exterior. Por ejemplo ab | cd hubiese sido suficiente n el ejemplo anterior. Los paréntesis se deberán usar para expresiones más complejas, tales como ( ab | cd+ )?( ef )* la cual coincide con tales literales como abefef, efefef, cdef, cddd, pero no abc, abcd, o abcdef.

 

ESPECIFICACIÓN DE SENSITIVIDAD DE CONTEXTO

Lex reconoce una pequeña cantidad del contexto que le rodea. Los dos operadores más simples para éstos son el signo de intercalación ( ^ ) y el signo de dólar ( $ ). Si el primer carácter de una expresión es un signo ^, entonces la expresión sólo coincide al principio de la línea (después de un carácter newline, o al principio del input). Esto nunca se puede confundir con el otro significado del signo ^, complementación de las clases de caracteres, puesto que la complementación sólo se aplica dentro de corchetes. Si el primer carácter es el signo de dólar, la expresión sólo coincide al final de una línea (cuando va seguido inmediatamente de un carácter newline). Este último operador es un caso especial del operador barra ( / ) , el cual indica contexto al final.

La expresión ab/cd coincide con el literal ab, pero sólo si va seguido de cd. Por lo tanto ab$ es lo mismo que ab/\n El contexto de la izquierda se maneja en lex especificando las condiciones start según se explica en la sección “Especificación de sensibilidad de contexto izquierdo “. Si una orden sólo se va a ejecutar cuando el interprete del autómata de lex está en la condición x start, la orden se deberá incluir entre corchetes de ángulos: <x> Si consideramos que estamos al comienzo de una línea que es el comienzo de la condición ONE, entonces el operador ( ^ ) será equivalente a <ONE> Las condiciones start se explican con detalles más tarde.

 

ESPECIFICACIÓN DE REPETICIÓN DE EXPRESIONES.

Las llaves ( { y } ) especifican o bien repeticiones ( si éstas incluyen números) o definición de expansión (si incluyen un nombre). Por ejemplo {dígito} busca un literal predefinido llamado dígito y lo inserta en la expresión, en ese punto.

ESPECIFICAR DEFINICIONES.

Las definiciones se dan en la primera parte del input de lex, antes de las órdenes. En contraste, a{1,5} busca de una a cinco apariciones del carácter “a”. Finalmente, un signo de tanto por ciento inicial ( % ) es especial puesto que es el separador para los segmentos fuente de lex.

ESPECIFICACIÓN DE ACCIONES.

Cuando una expresión coincide con un modelo de texto en el input lex ejecuta la acción correspondiente. Esta sección describe algunas características de lex, las cuales ayudan a escribir acciones. Nótese que hay una acción por defecto, la cual consiste en copiar el input en el output. Esto se lleva a cabo en todos los literales que de otro modo no coincidirían. Por lo tanto el usuario de lex que desee absorber el input completo, sin producir ningún output, debe proporcionar órdenes para hacer que coincida todo. Cuando se está usando lex con el yacc, ésta es la situación normal. Se puede tener en cuenta qué acciones son las que se hacen en vez de copiar el input en el output; por lo tanto, en general, una orden que simplemente copia se puede omitir. Una de las cosas más simples que se pueden hacer es ignorar el input. Especificar una sentencia nula de C; como una acción produce este resultado. La orden frecuente es [ \ t \ n] ; la cual hace que se ignoren tres caracteres de espaciado (espacio en blanco, tabulador, y newline).

Otra forma fácil de evitar el escribir acciones es usar el carácter de repetición de acción, | , el cual indica que la acción de esta orden es la acción para la orden siguiente. El ejemplo previo también se podía haber escrito: “ ” | “\ t” | “\ n” ; con el mismo resultado, aunque en un estilo diferente. Las comillas alrededor de \ n y \ t no son necesarias.

En acciones más complejas, a menudo se quiere conocer el texto actual que coincida con algunas expresiones como: [ a-z ] + lex deja este texto en una matriz de caracteres externos llamada yytext. Por lo tanto, para imprimir el nombre localizado, una orden como [ a-z ] + printf (“%s”  yytext); imprime el literal de yytext.

La función C printf acepta un argumento de formato y datos para imprimir; en este caso , el formato es print literal donde el signo de tanto por ciento ( % ) indica conversión de datos, y la s indica el tipo de literal, y los datos son los caracteres de yytext. Por lo tanto esto simplemente coloca el literal que ha coincidido en el output. Esta acción es tan común que se puede escribir como ECHO. Por ejemplo [ a-z ] + ECHO; es lo mismo que el ejemplo anterior.

Por ejemplo, si hay una orden que coincide con “read”, ésta normalmente coincidirá con las apariciones de “read” contenidas en “bread” o en “readjust”; para evitar esto, una orden de la forma [ a-z ] + es necesaria. Esto se explica más ampliamente a continuación. A veces es más conveniente conocer el final de lo que se ha encontrado; aquí lex también proporciona un total del número de caracteres que coinciden en la variable yyleng.

Para contar el número de palabras y el número de caracteres en las palabras del input, será necesario escribir [ a-zA-Z ] + {words++ ; chars += yyleng;} lo cual acumula en las variables chars el número de caracteres que hay en las palabras reconocidas. Al último carácter del literal que ha coincidido se puede acceder por medio de yytext[ yyleng - 1]

Ocasionalmente, una acción lex puede decidir que una orden no ha reconocido el alcance correcto de los caracteres. Para ayudar a resolver esta situación hay dos rutinas. La primera, yymore( ) se puede llamar para indicar que la siguiente expresión de input reconocida se va a añadir al final de este input. Normalmente, el siguiente literal de input escribirá encima de la entrada actual en yytext. Segundo, yyless( n ) se puede llamar para indicar que no todos los caracteres que coinciden con la expresión actual se quieren en ese momento. El argumento n indica el número de caracteres de yytext que se van a retener. Otros caracteres que han coincidido previamente se devuelven al input. Esto proporciona el mismo tipo de anticipación ofrecido por el operador barra ( / ), pero en una forma diferente.

Lex permite el acceso a las rutinas de I/O que usa. Estas incluyen:

1. input ( ) el cual devuelve el siguiente carácter de input;

2. output ( ) el cual escribe el carácter c en el output;

3. unput (c) el cual mete el carácter c de nuevo dentro del input que se va a leer después por input ( ).

Por defecto, estas rutinas se proporcionan como definiciones macro, pero el usuario puede contrarrestarlas y proporcionar versiones privadas. Estas rutinas definen la relación entre los ficheros externos y los caracteres internos, y todas se deben retener o modificar consistentemente. Estas se pueden redefinir, para hacer que el input o el output sea transmitido a o de lugares extraños, incluyendo otros programas o memoria internas pero el conjunto de caracteres que se usa debe ser consistente en todas las rutinas; un valor de cero devuelto por input tiene que significar final de fichero, y la relación entre unput o input debe ser retenido o no funcionará el “loockahead”. Lex no mira hacia delante si no es necesario, pero cada orden que contiene una barra ( / ) o que termina en uno de los siguientes caracteres implica esto: + * ? $

El “loockahead” se usa para que coincida una expresión que es un prefijo de otra expresión. Ver a continuación una explicación del conjunto de caracteres que usa lex. La librería estándar de lex impone un límite de 100 caracteres.

Otra rutina de la librería de lex es que a veces es necesaria redefinir es yywrap ( ) la cual se llama siempre que lex llega a final de fichero.

 

MANEJO DE ÓRDENES FUENTES AMBIGUAS

Lex puede manejar especificaciones ambiguas. Cuando más de una expresión puede coincidir con el input en curso, lex selecciona de la forma siguiente: Se prefiere la coincidencia más larga. De entre las órdenes que coinciden en el mismo número de caracteres, se prefiere la primera orden especificada.

Por ejemplo: suponga que se especifican las órdenes siguientes: integer keyword action ... ; [ a-z ] + identifier action ... ; Si el input es integers, se toma como un identificador, puesto que [ a-z ] + coincide con ocho caracteres mientras que integer sólo coincide con siete.

Si el input es integer, ambas órdenes coinciden en siete caracteres, y se selecciona la orden keyword porque ésta ha sido especificada primero.

Cualquier cosa más corta (por ejemplo, int) no coincide con la expresión integer, por lo tanto se usa la interpretación identifier. El principio de la preferencia de la coincidencia más larga hace que ciertas construcciones sean peligrosas, tales como la siguiente: .*

TIPOS DE ERRORES DE LEX

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 carácter. 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 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.

Para el ejemplo de borrado de un carácter, tenemos que los sinónimos de ELSE son ELS, ELE, ESE, y LSE. Por tanto, si incluimos en nuestro analizador una rutina de recuperación de errores debidos a omisión de caracteres, cualquiera de estos sinónimos sería aceptado en lugar del lexema ELSE, se emitiría la correspondiente advertencia, y el proceso continuaría asumiendo que se ha leído el token <pal_res_ELSE>.

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.

 

COMPILAR EN LEX

Hay dos pasos al compilar un programa fuente lex:

Ø       Primero, el programa lex fuente tiene que ser convertido en un programa regenerado en el lenguaje de propósito general. Entonces éste programa tiene que ser compilado y cargado, normalmente con una librería de subrutinas lex. El programa generado está en un fichero llamado lex.yy.c. La librería I/O está definida en términos de la librería estándar C. A la librería se accede por medio del flag de la carga -ll. Por lo tanto un conjunto de comandos apropiados es lex source cc lex.yy.cll

Ø       El programa resultante se pone en el fichero usual a.out para ser ejecutado posteriormente. Aunque las rutinas I/O por defecto de lex usan la librería estándar C, el autómata del lex no lo hace. Si se especifican las versiones privadas de input, output, y unput, la librería se puede evitar.

Hosted by www.Geocities.ws

1