Análisis Léxico M.C. Juan Carlos Olivares Rojas
Agenda
• Introducción a los Autómatas finitos y expresiones regulares. • Analizador de léxico. • Manejo de localidades temporales de memoria (buffers). • Creación de tablas de símbolos. • Manejo de errores léxicos. • Generadores de código léxico: Lexy Flex.
Ensamblado en Java
• ¿Qué es un autómata? Es un modelo matemático que sirve para determinar si una cadena pertenece a un lenguaje no. • A = (Q, Σ, &, F) donde • • • •
Q = conjunto de estados Σ= alfabeto de entrada & = funciones de transición F = conjunto de estados de aceptación
AFD
• La característica que tienen los Autómatas Finitos Deterministas (AFD) es que solo existe una función de transición definida para un símbolo de entrada. Esto elimina ambigüedades • Una expresión regular es una forma abreviada de representar lenguajes: • a Representa el lenguaje de las letras a • a* Representa el lenguaje que tiene 0 hasta n a’s
AFD
• El principio de diseño de un Lenguaje de Programación radica en la utilización de un autómata genérico y de ese autómata partir de varios subautómatas. • Los autómatas suelen representarse expresiones regulares. Ejemplo: • Identificador: letra (letra | digito | gb)*
con
AFD
• Generar la expresión regular para identificar URL válidas. • Ejemplo: • http://www.patito.com/recurso.php?id=0 • ftp://ftp.sindominio.com/pub/archivo • file://casa.decera.com/recurso+20+dos
AFD
• Dada la siguiente expresión regular como queda el autómata que lo representa: • Alpha (alpha | num | sim)* • Donde alpha es un carácter de a..Z • Num es un dígito de 0 a 9 • Sim es uno de los siguientes símbolos
AFD
• ¿Cómo se programa un autómata? • Los autómatas tienen su representación en forma de grafos que a su vez se representan en forma de tablas de transiciones, se cuenta con un estado inicial y una función de siguiente estado. Al terminar de leer la cadena se verifica si el estado es algún estado de aceptación.
Práctica 5
• Programar un autómata para reconocer la URLs que son válidas. • Se deberá tener una matriz de transiciones y una función de estado siguiente. No se permitirán validaciones por otros medios. • Se deberán leer cadenas contenidas en un archivo de texto plano y deberá devolver que cadenas si son URLs
Expresiones Regulares y Gramáticas
• ¿Qué similitudes existen entre las expresiones regulares y gramáticas? • Una gramática sirve para generar cadenas de un determinado lenguaje pero también sirve para generarla. • Existente una relación uno a uno entre Lenguajes, Autómatas, Expresiones regulares y gramáticas.
Analizador Léxico
• El análisis léxico es la primera fase de la compilación. • Un analizador léxico: • Lee caracteres de entrada y generar como salida una secuencia de componentes léxicos (generalmente representados por números).
Analizador Léxico
• Un analizador léxico:
• Elimina espacios en blanco (considerados por el lenguaje como tabuladores, etc.) • Eliminar comentarios • Proporcionar información acerca de errores léxicos (caracteres inexistentes o tokens inexistentes)
Tabla de Símbolos
• La tabla de símbolos se compone de un lexema y un identificador del componente léxico. • Los componentes léxicos se conforman de un patrón (expresión regular que lo valida). • El patrón se codifica como un autómata.
Tokens, Lexemas y Patrones
Análisis Léxico
• También recibe el análisis lineal, se llama léxico o exploración. • Ejemplo: • Posicion:= inicial + velocidad * 60 • Posicion: identificador • := símbolo de asignación • Inicial: identificador
• • • •
Análisis Léxico
+: signo de suma Velocidad: identificador *: signo de multiplicación 60: numero
• Las palabras clave de muchos lenguajes son lexemas tal cual. Por ejemplo para reconocer un token if, se necesita de un patrón “if”.
Analizador Léxico
• La tabla de símbolo debe insertar y buscar componentes léxicos (aunque en el análisis léxico sólo se inserten, se debe recordar que la tabla de símbolos es utilizada por el analizador sintáctico y por otras fases del proceso de traducción. • Los componentes léxicos se tratan como terminales de la gramática del lenguaje fuente. Existen muchas herramientas para utilizar expresiones regulares.
Definiciones Regulares
• Dan nombres a las expresiones regulares. Permiten referenciar las expresiones regulares recursivamente Ejemplo: Digito 1|2|3|4|5|6|7|8|9|0 Entero dígito+ Decimal dígito+ | .dígito+ E (+ |- | ε) dígito+ • Real entero (decimal | ε) • • • •
Abreviaturas
• * cero o más casos
• + uno o más casos • ? 0 ó 1 vez (opcional) • [a-zA-Z] mayúsculas y minúsculas • [0-9] dígitos
Definiciones Regulares
• Digito[0-9]
• Entero digito+ • Decimal .digito+exponente? • Exponente (E | e) (+|-)?digito+ • Real entero decimal?
Analizador LĂŠxico
Examen 2
• Entrega: viernes 9
• Análisis de Requerimientos dado un archivo *.class correcto, encontrar los mnemónicos que tiene disponible el ensamblador (alias javap – c Archivo) • Complejidad al detectar el encabezado (formato *.class) y después la complejidad del código
Examen 2
Examen
• Se puede observar que de este pequeño hola mundo se tienen dos métodos. • El primero de ellos de 5 bytes, revisando sus mnemónicos se puede observar que su códigos de operación en byte es: • 0x2a • 0xb7 0x00 0x01 • b1
• • • • •
BB FE AF EA 77 03 05 2a b7 00 01 b1 12 Formato valido aload_o Invokespecial 0 1 Return
Examen 2
Examen 2
• Esto se puede buscar en el archivo *.class. • El detalle radica en que el código no viene sólo sino en una estructura Attribute denominada code_attribute (ver unidad pasada). Nótese por ejemplo que un byte antes del código marcado nos indica el tamaño del código de ese método.
Examen 2
• Reformulación del proyecto. Dado nuestro propio formato comenzará de la siguiente forma: 4 bytes de número mágico: BB FE AF EA, 2 bytes para indicar la versión y subversión del compilado, 2 bytes para indicar el tamaño del código, instrucciones del ensamblador de java y un byte al final como código de comprobación (tamaño total del archivo % 255).
Examen 2
• Se deberá validar que sus archivos *.obj tengan este formato. Se hará la impresión de los mnemónicos contenidos (puede ser modo texto o visual). • Se ocupa conocer el tamaño de los mnemónicos • ¿Para el viernes autómata o examen 2? Por lo tanto que queda para el lunes.
Mnemónicos en Java
• astore <varnum>: saca de la pila una referencia. • baload: recupera un byte de un arreglo de bytes y lo convierte a un entero que se coloca en la pila de operandos. • bipush <n>: coloca un entero de 8 bits en la pila (lo convierte a un entero de 32 bits)
Mnemónicos en Java
• d2f: saca de la pila un flotante de doble presición para convertile a un flotante sencillo y volverlo a colocar en la pila (esta operación puede causar pérdida de precisión). • dadd: toma dos dobles de la pila, los suma y coloca el resultado en la pila. • dup_x1: duplica el valor de la cima de la pila y la coloca una posición debajo.
Mnemónicos en Java
• fneg: niega un flotante.
• goto <label>: brinca a la dirección especificada por la etiqueta (cadena de texto), label ocupa dos bytes por lo que esta instrucción es de 3 bytes. • iand: realiza una operación lógica “Y” entre dos enteros en la cima de la pila, el resultado lo deja en la pila.
Mnemónicos en Java
• ifeq <label>: realiza un pop en la pila para comparar si el valor apuntado por etiqueta es igual al de la pila. Esta es una instrucción de 3 bytes • istore <varnum>: saca un valor de la pila y lo almacena en una variable local. • jsr_w <label>: salta a una subrutina, label hace referencia a un valor doble, por lo que esta instrucción es de 5 bytes.
Manejo de localidades temporales de memoria (buffers) • La forma más fácil de realizar el escanner de un código es a través de carácter por carácter pero esto es sumamente ineficiente. • La forma más eficiente es realizar una copia a la memoria de todo el código fuente.
Manejo de Buffers
• La desventaja de este último enfoque es que la gran mayoría de las ocasiones es impráctico por las dimensiones de los programas. Para solucionar este problema se sugiere utilizar buffers. • Existen muchas formas de dividir el trabajo, pero siempre se deberá llevar dos punteros, uno al carácter actual y otro al inicial del lexema.
Creación de Tabla de Símbolos
• En general el proceso de análisis léxico puede describirse simplemente como el reconocimiento de caracteres de un lenguaje para generar una tabla de símbolos. • El primer paso consiste en crear un escáner, el cual se encarga de verificar que no existan caracteres no presentes en el lenguaje.
Tabla de Símbolos
• La tabla de símbolos va a guardar cada palabra analizada, la va identificar como un lexema y le va asociar un identificador numérico para posteriormente utilizarlo. • La tabla de símbolos debe estar en memoria para realizar un análisis rápido.
Errores Léxicos
• Son pocos los errores que se pueden detectar al hacer análisis léxico • Por ejemplo:
– fi (a == f(x))
• ¿Es error léxico? No, es error sintáctico • Puede existir algún error si ninguno de los patrones no concuerda con el prefijo de entrada.
Técnicas de Recuperación de Errores • Borrar un carácter extraño • Insertar un carácter que falta • Reemplazar un carácter incorrecto por otro correcto • Intercambiar dos caracteres adyacentes
Técnicas para realizar analizadores léxicos
• Utilizar un analizador léxico como LEX. El generador se encarga de manejar buffers • Escribir el analizador en un lenguaje de alto nivel haciendo uso de la E/S del lenguaje • Escribir el lenguaje ensamblador y manejar explícitamente la E/S
Patrones en Java
• El lenguaje Java es un lenguaje de alto nivel de propósito general que cuenta entre sus cosas con un motor para reconocimento de patrones de texto. • El paquete javax.util.regex.*; el cual dispone de dos clases: Pattern y Matcher.
Patrones en Java
• La clase Pattern se utiliza para especificar los patrones y la clase Matcher se utiliza para procesar las cadenas y ver si coinciden con el patrón. • El método compile de la clase Pattern permite especificar la expresión regular utilizando prácticamente la misma simbología de comodines de expresiones regulares.
Patrones en Java
• Un objeto de la clase Matcher se crea a través del método matcher() del objeto instanciado de la clase Pattern. • La clase matcher tiene los siguientes métodos: matches para una coincidencia exacta, lookingAt cuando se encuentra el patrón en parte de la cadena, find permite buscar subcadenas que coincidan con el patrón.
Patrones en Java
• El método find permite utilizar los métodos start y end para encontrar la primera y última coincidencia. • El método replaceAll(), permite cambiar una subcadena por otra.
Patrones en Java
import java.util.regex.*; public class ValidarEmail { public static void main(String[] args) throws Exception { String cadena = â&#x20AC;&#x153;jcolivar@hotmail.com"; Pattern p = Pattern.compile("\\w\\+@\\w\\.\\w"); Matcher m = p.matcher(input); if (m.find()) System.err.println("Las direcciones email no empiezan por punto o @"); } }
Generadores de Código Léxico
• FLEX es la versión de software libre del popular generador de analizadores léxicos LEX para sistemas *NIX, genera código C aunque existen otras herramientas que generan código en otros lenguajes (Jflex para Java) • Analizador.lex flex lex.yy.c Programa ejecutable analizador • $gcc lex.yy.c –o analizador –l fl
gcc
Estructura Lex
%{ Definiciones globales ‘C’ }% Definiciones flex %% Acciones %% Código ‘C’ auxiliar
Definiciones de Expresiones Regulares • %% Separadores de secciones • Def expresión • Acciones • {def} {código ‘C’ asociado} • “@”{código ‘C’ asociado}
Ejemplo Scanner Flotante
%{ #include <stdio.h> int ocurrencias; %} digito [0-9] punto [\.] exp [eE] signo[\+\-] digitos {digito}+
Ejemplo de Scanner Flotante
decimal {punto} {digitos}({exp}{signo} {digitos})? flotante {digitos}{decimal}? %% {flotante} { printf(“flotante encontrado\n”); ocurrenicas++; } “@” { printf(“Arroba\n”); } . { printf(“Inválido: %s\n”, yytext); } %%
Ejemplo de Scanner Flotante
int main(int argc, char* argv[]) { FILE *f; F = fopen(argv[1], “r”); yyin= f; while(yylex()); printf(“%dflotantes encontrados\n”, ocurrencias); fclose(f); return 0; }
JFlex
• Es otra forma de incluir expresiones regulares para crear scanners pero ahora con lenguaje java. • Se tiene que manejar una sintaxis muy similar a lex con sus respectivas variantes. • Las expresiones regulares se definen dentro de un archivo con extension .lex o jflex (realmente la extensión no importa). A continuación se muestra un ejemplo.
JFlex
/*Aqui van todas las bibliotecas*/ import javax.swing.JOptionPane; %% /*Definiciones regulares configuracion*/ %class Flotante %unicode %full %int
y
parametros
de
%line %column
JFlex
%{ /*Codigo Java genĂŠrico*/ int ocurrencias; public int getOcurrencias(){ return ocurrencias; }
%}
JFlex
/*Definiciones regulares*/ digito=[:digit:] blancos=[\n\r\t\f\b] punto =[.] exp=[eE] signo=[+-] digitos={digito}+ decimal={punto}{digitos}(("e"|"E"){signo} {digitos})?
JFlex
flotante={signo}?{digitos}{decimal}? /*Sección de acciones*/ %% /*Palabras reservadas */ <YYINITIAL> "break" { JOptionPane.showMessageDialog(null, "Se encontro un break"); } <YYINITIAL> { {flotante}
{
JFlex
JOptionPane.showMessageDialog(null, encontro un flotante: "+ yytext()); ocurrencias++; }
"Se
{blancos}{/*se ignora*/} . { /*ignoramos el token*/ } } โ ข //Falta construir la clase principal o bien incrustarla dentro del cรณdigo main
JFlex
import java.io.*; public class ScannerFlotante { public static void main(String[] args) { try { FileReader arch = new FileReader(args[0]); Flotante f = new Flotante(arch); f.yylex(); System.out.print("Numero de flotantes encontrados:" + f.getOcurrencias()); } catch (Exception e) {
JFlex
e.printStackTrace(); } } }
• La invocación de Jflex se realiza jflex archivo.lex, sino se pasan argumentos se abre una interface gráfica. • La clase generada se llama Flotante.java (si no se expecifica se crea una clase Yylex.java)
JFlex
• Al igual que lex, se puede complementar el código con algún otro código de Java, por ejemplo APIs para el manejo de gráficos. • Jflex genera autómatas finitos no determinista que luego convierte a autómatas finitos determinista para posteriormente minimizar su estado.
Referencias
• Aho, Sethi, Ullman. Compiladores Principios, técnicas y herramientas Ed. Addison Wesley. • Beck,. Software de Sistemas, Introducción a la programación de Sistemas Ed. AddisonWesley Iberoamericana. • Kenneth C. Louden. Construcción de compiladores Principios y práctica. Ed. Thomson.
Referencias
â&#x20AC;˘ Java Virtual Machine Specification
多Preguntas?