Sunteți pe pagina 1din 9

ESCUELA UNIVERSITARIA DE INGENIERÍA TÉCNICA

INFORMÁTICA DE OVIEDO
CURSO ACADÉMICO: 2005-2006

TEORÍA DE AUTÓMATAS

Práctica 2: Analizador léxico


1. JLEX: EL ANALIZADOR LÉXICO

1.1 EL ANALIZADOR LÉXICO JLE€`DSÐ


Genera un fichero llamado analizador.lex.java. Por comodidad, lo renombramos a
analizador.java (mv analizador.lex.java analizador.java)
3. Se compila el fichero anterior para obtener nuestro analizador léxico en código
ejecutable.

javac analizador.java
Obtendremos una clase (Analex.class u otro nombre con extensión .class) que
contiene al analizador léxico listo para empezar a trabajar con él.

1.3 UTILIZACIÓN DEL ANALIZADOR LÉXICO CONSEGUIDO


Para usar el analizador léxico, realizaremos los siguientes pasos:
1. Editamos un fichero que contendrá el texto que queremos analizar. En algunos casos
en vez de crear un fichero y pasárselo al analizador podemos dar el texto al analizador
a través de teclado.
2. El analizador va leyendo el fichero y si encuentra que la cadena leída es denotada por
alguna de las expresiones regulares del fichero de especificación entonces ejecuta la
acción asociada a esa expresión y sigue leyendo del fichero. Normalmente la salida irá
a un fichero de salida, pero también puede ir a la pantalla.

2. FICHERO DE ESPECIFICACIÓN
Un fichero de especificación JLex tiene la siguiente organización
Código de usuario
%%
directivas JLex
%%
Reglas para las expresiones regulares
Los caracteres %% distinguen una sección de otra. Deben estar colocados al principio
de la línea y el resto de la línea no debe usarse.

2.1 SECCIÓN CÓDIGO DE USUARIO


El código que se escriba aquí, debe ser código java correcto ya que se copia
directamente y sin modificaciones al principio del fichero analizador.java. Aquí es el sitio
para implementar clases auxiliares que necesite o importar las clases que se utilicen. Si no lo
hace aquí, debería editar el fichero java y modificar código generado por una herramienta
(JLex) cosa no siempre sencilla.
2.2 SECCIÓN DIRECTIVAS JLEX
Aunque en esta sección pueden definirse muchas más cosas, nuestro interés se centrará,
por ahora, en la definición de macros. Cada macro está en una línea y consiste en una
definición de la forma <nombreMacro>=<DefiniciónMacro>. El nombre de una macro
debe ser un identificador válido. Y la definición, una expresión regular.
Estas macros se utilizarán posteriormente para simplificar la especificación del
analizador. Una vez que a una expresión regular le ponemos un nombre, después es posible
referirse a ella a través de dicho nombre y ahorrarse el escribir de nueva toda la expresión.
Ejemplo:
DIGITO=[0-9]
IDENT=[a-z][a-z0-9]*

Para hacer referencia posteriormente a estas definiciones basta con poner el nombre
entre paréntesis: {DIGITO} o {IDENT}. En el primer caso DIGITO denota a cadenas de un
solo carácter numérico e IDENT denota cadenas de cualquier longitud cuyo primer carácter
sea alfabético y el resto, si lo hubiera, alfanumérico.
Si posteriormente escribimos {DIGIT}+"."{DIGIT}* es equivalente a escribir la
expresión regular ([0-9])+"."([0-9])* que denota cadenas de uno o más dígitos seguidos por
un punto y por cero o más dígitos (por ejemplo: 23.46, 1.1, 34. )

2.3 SECCIÓN REGLAS PARA LAS EXPRESIONES REGULARES


La tercera parte del fichero de especificación JLex consiste en una serie reglas para
trocear cada línea del fichero de entrada (o del teclado) en tokens o componentes léxicos, que
son secuencias de caracteres con significado.
Por ejemplo 3.3 es una secuencia de caracteres con significado (es un número).
Estas reglas especifican expresiones regulares y les asocian acciones (código java).
Tienen la forma: Expresión Acción
Si existe más de una regla que encaje con una entrada, el conflicto se resuelve eligiendo
aquella regla que encaje con la cadena más larga.
Por ejemplo, es posible tener:
{DIGITO} Unas Acciones
{INTEGER} Otras

Si la entrada fuese 33, se elegiría la segunda regla, ya que la primera capturaría cada
dígito por separado, mientras la segunda capturaría la cadena entera.
Si aún así, sigue habiendo conflicto, se elige la primera regla que se encuentre en el
fichero que encaje con la entrada.
Las reglas del fichero de especificación deben cubrir cualquier posible entrada, ya que
si no encuentra una regla para una entrada, el analizador léxico eleva un error. Para garantizar
esta condición, es suficiente poner como última regla:

. Acciones a realizar cuando llega algo que no encaje en las reglas anteriores.
2.3.1 Expresiones Regulares en JLex
Las expresiones regulares no deben contener espacios en blanco, que se interpretan
como final de la expresión regular. A menos que vayan entre comillas dobles : “ “ lo que se
interpreta como una expresión regular que encaja con un espacio en blanco en el texto.
El alfabeto para JLex es el conjunto de caracteres ASCII y los siguientes son caracteres
especiales o metacaracteres:
? * + | ( ) ^ $ . [ ] { } “ \

Cualquier otro carácter, se representa a sí mismo. Y la concatenación se expresa


simplemente escribiéndolos juntos.

2.3.2 Significado de los metacaracteres


| Disyunción
\b Backspace
\t Tabulador
\n Salto de línea
$ Final de línea. Si se pone al final de una expresión regular, encaja
únicamente si la cadena está al final de la línea
. Cualquier carácter excepto nueva línea
“ …” Cualquier carácter especial entre comillas dobles pierde su significado
especial
{nombre} Nombre entre llaves es la extensión de una macro definida previamente
* 0 ó más repeticiones de la expresión regular precedente.
+ 1 ó más repeticiones de la expresión regular precedente.
? 0 ó 1 repeticiones de la expresión regular precedente.
() Los paréntesis se usan para agrupar expresiones regulares
[] Denotan conjuntos de caracteres y representa cualquiera de los que están
dentro. Si el primer símbolo es ^ la expresión regular se refiere a
cualquier carácter excepto los del conjunto. Es posible poner rangos [a-z]
Cualquier metacarácter entre “ ” pierde su significado. La única excepción es \” que
representa al carácter ”. También pierden su significado si van precedidos de \

2.3.3 Acciones en JLex


Puede ser cualquier código java entre { }. Si queremos acceder a la cadena de entrada
que provocó esta acción, se puede hacer con la función yytext() que la proporciona
3. EJEMPLOS

3.1 EJEMPLO 1
Se puede encontrar en el fichero ejemplo1.lex

import java.lang.System;
class Analex {
public static void main(String argv[])
throws java.io.IOException {
Yylex yy = new Yylex(System.in);
while (yy.yylex() != null) {}
}
}

class Yytoken {
Yytoken () {}
}
%%
%%
a {System.out.println("A");}
b {System.out.println("B");}
\n { }
.+ {System.out.println(yytext());}

Una vez procesado con JLex, tendremos un programa que lee de teclado y reproduce el
carácter leído a la salida (pantalla). Excepciones: la letra a, que imprime A, la letra b (imprime
B) y el salto de línea, que no lo reproduce.
Sección de declaración, sirve para dar nombre a ciertas expresiones de forma que se
simplifique la escritura de las reglas o para declarar variables que usen más adelante. En
nuestro ejemplo esta sección está vacía, pero más adelante veremos en otros ejemplos cómo
se puede usar.
Las reglas son las que especifican qué acción se debe realizar cuando se encuentran
cadenas que responden a cada expresión regular. En nuestro caso, las expresiones regulares
son muy sencillas: la expresión regular a denota al carácter "a", la expresión regular b denota
"b", \n, denota el salto de línea y .+ denota cualquier combinación de símbolos (salvo el \n).
Veremos que las expresiones pueden ser mucho más complicadas (en realidad se
pueden utilizar todos los operadores que vimos en la primera práctica con XEmacs). Las
acciones que hemos incluido en nuestro ejemplo también son muy sencillas. Hemos usado la
función System.out.println, que se usa para escribir en la salida estándar (la pantalla) y hemos
indicado que cuando se encuentre "a" se escriba "A" y viceversa.
La primera parte incluye el código de usuario, que indica qué debe hacer realmente el
programa. La situación más sencilla es la que aparece en nuestro ejemplo. La función main
llama a la función yylex. Esta función yylex es la que genera lex a partir de las reglas que
hemos especificado en el fichero de entrada. Y lo que hace es precisamente procesar la
entrada e ir aplicándolas. Más adelante veremos código de usuario un poco más elaborado.
3.1.1 Funcionamiento
La obtención de un programa ejecutable a partir de una especificación lex incluye
varios pasos:
Obtener código java a partir de la especificación lex:
java JLex.Main ejemplo1.lex
En este ejemplo, se genera un fichero llamado ejemplo1.lex.java. Se recomienda
renombrarlo (mv ejemplo1.lex.java ejemplo1.java). Compilar el código java
generado, para obtener el programa ejecutable.
Para compilarlo: javac ejemplo1.java. Generará un conjunto de clases (ficheros
.class). Uno de ellos es Analex.class que contiene la función main.
Ejecución. Tras los dos pasos anteriores tendremos un programa que lee de la entrada
estándar y va aplicando las reglas que hemos escrito. Para ejecutarlo: java Analex y a
continuación escribimos texto. Veremos cómo el programa vuelve a escribirlo convirtiendo la
a y b minúsculas en mayúsculas. Para finalizar la conversión pulsaremos Ctrl+D, que indicará
al programa que la entrada ha finalizado.
También podemos utilizar el programa sobre un fichero ya escrito, en lugar de sobre el
texto que vamos escribiendo. Para ello, habrá que redireccionar la entrada estándar del
programa del siguiente modo: java Analex < prueba siendo prueba el fichero que
queremos procesar.

3.2 EJEMPLO 2
La acción más fundamental que puede llevarse a cabo con JLex es la sustitución. En el
ejemplo anterior hemos visto cómo el programa generado sustituía cada aparición de "a" por
"A" y de “b” por “B”. Estas sustituciones pueden involucrar simplemente caracteres, cadenas
o expresiones regulares complejas.
Por ejemplo, el siguiente código serviría para cambiar cada aparición de "np" por "mp"
y de “nb” por “mb” (ejemplo2.lex).

import java.lang.System;
class Analex {
public static void main(String argv[])
throws java.io.IOException {
Yylex yy = new Yylex(System.in);
while (yy.yylex() != null){}
}
}

class Yytoken {Yytoken () {}}


%%
%%
np {System.out.print("mp");}
nb {System.out.print("mb");}
\n {System.out.print(yytext());}
. {System.out.print(yytext());}
4. EJERCICIOS
Es posible que el uso más extendido de lex sea la implementación de analizadores
léxicos. Se trata de identificar los elementos mínimos (también llamados "tokens") de un
lenguaje, como pueden ser palabras reservadas, números, nombres de variable, operadores,
signos de puntuación,... Una vez conocidos los elementos que aparecen en un cierto
programa, el siguiente paso sería ver cómo se combinan entre sí (análisis sintáctico), pero eso
se verá en la siguiente práctica. De momento, vamos a ver un ejemplo sencillo de análisis
léxico.
Supongamos que definimos un lenguaje para el control del movimiento de un objeto.
Este lenguaje, que llamaremos movelan, ofrece operaciones para ordenar el desplazamiento
y rotación de un objeto. En este primer ejemplo, sólo se reconocerán los tokens del programa
(las órdenes, las distancias y las unidades) y se imprimirá por pantalla lo que se vaya
encontrando. El esqueleto de partida está en el fichero movelan.lex
Dicho lenguaje será capaz de ordenar el avance (AVA) y retroceso (RET) de una
distancia fijo, indicada mediante un número entero o flotante con su correspondiente unidad.
Así, un programa escrito en movelan, tendría el siguiente aspecto:
AVA 3,5 m;

RET 80 cm;

……

En el siguiente ejemplo, se presenta un analizador léxico para el lenguaje de


programación movelan.

import java.lang.System;
class Analex {
public static void main(String argv[])
throws java.io.IOException {
Yylex yy = new Yylex(System.in);
while (yy.yylex() != null) {}
}
}

class Yytoken { Yytoken () {}}


%%
DIGIT=[0-9]
INTEGER=(\+|-)?{DIGIT}+
%%
";" {System.out.print("fin sentencia ");}
AVA {System.out.print("Avanzar ");}
RET {System.out.print("Retroceder ");}
{INTEGER} {System.out.print("entero<"+yytext()+"> ");}
cm|m {System.out.print("unidad<"+yytext()+"> ");}
(" "|\t)+ {}
\n {System.out.println("\n");}
. {System.out.println("ERROR LEXICO!");}
Observe cómo hemos utilizado el operador de repetición "+" para construir una
expresión regular que denote a los números enteros o el operador ? para hacer el signo
opcional.
Es posible utilizar todos los operadores de expresiones regulares que vimos en la
primera práctica, empleando la misma sintaxis que en la aplicación RexExp Coach.

4.1 EJERCICIO 1
Pruebe el analizador léxico anterior con el fichero PruebaMovelan. ¿A qué se deben los
mensajes de error que aparecen? Justifica cada uno de ellos.

4.2 EJERCICIO 2
Modifique el fichero .lex anterior para que reconozca, además de los enteros, los
números reales (con punto decimal, con o sin signo, con o sin parte entera, pero al menos con
parte entera o decimal). Añada también las unidades de giro grados (GRA) y radianes (RAD).
En el proceso de análisis, escribirá, de forma análoga a como se hace con los enteros, el
mensaje real <valor>, por cada número real que encuentre en el fichero de entrada. Para
las nuevas unidades el mensaje será unidad de giro<grados> o unidad de
giro<radianes>.

4.3 EJERCICIO 3
Añada, a los operadores del lenguaje ensam, la operación de rotación (ROT) y la
repetición de órdenes (REP) así como el paréntesis abierto y el cerrado. Imprima por pantalla
los mensajes Rotar, Repetir, paréntesis abierto y paréntesis cerrado,
respectivamente.

4.4 EJERCICIO 4
Modificar las expresiones regulares para que reconozcan las órdenes aunque estén
formadas por la combinación de mayúsculas y minúsculas (p.e. aVa, ReT, Rep, rOT, ...).

S-ar putea să vă placă și