Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Dirección General
Subdirección Académica
INSTITUTO TECNOLÓGICO
SUPERIOR PROGRESO
PROGRAMA ACADÉMICO DE INGENIERÍA EN
SISTEMAS COMPUTACIONALES
ASIGNATURA
Lenguajes y Autómatas II
DOCENTE
Dr. Holzen Atocha Martínez García
TRABAJO
Evidencia 3 – Reporte de práctica (Analizador
Léxico/Sintáctico con símbolos)
PRESENTA
Br. Henry Efrain Torres Esquivel (04190031)
Índice.
Contenido
Objetivo de la práctica ...................................................................................................... 4
Introducción. ..................................................................................................................... 4
Marco teórico. ................................................................................................................... 5
¿Qué es analizador léxico? ........................................................................................... 5
Funciones. ..................................................................................................................... 5
PLY............................................................................................................................... 5
Analizador sintáctico. ................................................................................................... 6
¿Cómo funciona el analizador sintáctico? .................................................................... 6
Equipo y tecnologías utilizadas. ....................................................................................... 6
Procedimientos. ................................................................................................................ 7
Procedimientos: Primer programa. ............................................................................... 7
Procedimientos: Segundo programa. .......................................................................... 12
Pruebas y resultados. ...................................................................................................... 24
Resultado del funcionamiento: Primer programa. ...................................................... 24
Resultado del funcionamiento: Segundo programa. ................................................... 26
Conclusiones................................................................................................................... 30
Referencias. .................................................................................................................... 31
a) Portada.
Objetivo de la práctica:
Introducción.
Un analizador léxico es un programa, el cual cumple diferentes funciones
específicamente, reconocer un lenguaje, con sus caracteres de entrada, donde se encuentra
la cadena a analizar, reconocer sub – cadenas que correspondan a símbolos del lenguaje
y retornar los tokens correspondientes y sus atributos. Pero escribir analizadores léxicos
eficientes “amano” puede resultar una tarea tediosa y complicada, y para evitarla se han
creado herramientas de software, como lo ha implementado Python con su librería de
PLY.
Por otra parte, este trabajo tiene el objetivo de dar a conocer la lógica y funcionamiento
de un analizador léxico y sintáctico, además de permitir a nosotros, como alumnos,
entender mejor este tema, y de este modo adquirir la idea de la implementación de un
programa que ayude en su solución.
Marco teórico.
Funciones.
PLY.
La principal tarea de un analizador léxico es leer los caracteres de entrada del programa
fuente, agruparlos en lexemas y producir como salida una secuencia de tokens.
El analizador sintáctico obtiene una cadena de tokens del analizador léxico y verifica que
dicha cadena pueda generarse con la gramática para el lenguaje fuente. Una gramática
proporciona una especificación precisa y fácil de entender de un lenguaje de
programación.
En PLY se definen los patrones de los diferentes tokens que se desean reconocer, esto se
hace a través de expresiones regulares. Mientras que las producciones y acciones para
formar la gramática se definen a través de funciones.
Analizador sintáctico.
Analiza una cadena de símbolos de acuerdo a las reglas de una gramática formal. El
análisis sintáctico convierte el texto de entrada en otras estructuras (comúnmente árboles),
que son más útiles para el posterior análisis y capturan la jerarquía implícita de la entrada.
Un analizador léxico crea tokens de una secuencia de caracteres de entrada y son estos
tokens los que son procesados por el analizador sintáctico para construir la estructura de
datos, por ejemplo, un árbol de análisis o árboles de sintaxis abstracta.
Procedimientos.
Lo primero que debemos hacer es definir el listado de tokens que vamos a reconocer ya
asignarlo a la variable tokens.
El primer paso para el desarrollo del programa, es definir el listado de tokens que servirán
para reconocer las operaciones que se utilizarán. Se asignará a la variable: “tokens”.
Seguidamente, se asignaron los patrones que tendrán los tokens que se definieron. Existen
dos formas de definir las reglas para los tokens.
La primera, es con expresiones regulares, se agrega el prefijo: “t_” al token que queremos
definir y luego le especificamos la expresión regular, para esto se hace uso del módulo:
“re” de Python.
La otra forma es a través de funciones, esto nos sirve para manipular el valor del token
que procesamos. Por ejemplo, para los valores numéricos los retornamos con el tipo
apropiado, hacer sus validaciones, etc.
De igual manera, es importante definir también los caracteres que se van a ignorar.
Las funciones también llevan el prefijo: “t_” antes del nombre del token que queremos
procesar. La función recibe un parámetro, “t” en nuestro ejemplo, este contiene el valor
del token. Retornamos el valor ya procesado que deseamos, o no retornar nada si lo que
deseamos es ignorar el token (por ejemplo: comentarios, contadores, etc.).
Otra de las ventajas de Python es que en el mismo archivo podemos definir nuestro
análisis sintáctico haciendo uso de los tokens previamente definidos en la sección del
analizador léxico.
Ahora, procedemos a escribir nuestras producciones, aquí vemos otra de las ventajas de
Python, las acciones semánticas de nuestras producciones se hacen en forma de funciones.
• El nombre inicia con el prefijo: “_p”. El complemento del nombre queda a nuestra
discreción.
• Tiene un único parámetro: “t”, el cual es una tupla, en cada posición tiene el valor
de los terminales y no terminales de la producción.
• Haciendo uso del docstring de las funciones de Python, especificamos las
producciones que serán procesadas por la función.
• En el cuerpo de la función definimos la funcionalidad que deseamos.
Ejemplo:
En la imagen anterior, se sintetiza en p[0] (expresión) el valor del resultado de sumar los
valores de p[1] (expresión) y p[3] (expresión).
Por último, podemos manejar también las producciones de error para el manejo de errores
sintácticos. Como se logró visualizar en el último bloque de la imagen anterior.
Imagen 12. Construcción y lectura del analizador sintáctico para envío de contenido al
compilador.
Se creó un nuevo archivo de texto, utilizando nuestro editor, el archivo se nombró como:
“entrada.txt”. El contenido de este archivo es el siguiente:
El lenguaje de entrada.
Imagen 14. Ejemplo de explicación de lo que realiza cada función del programa.
El analizador léxico define los patrones para los tokens que deseamos reconocer.
Hacemos uso de expresiones regulares para identificar números, cadenas y comentarios.
Para esto hacemos uso del módulo: “re”, de Python (tiene relación con el primer programa
explicado en el apartado anterior).
Imagen 15. definición de los patrones para los tokens que se deseen reconocer.
Nótese que los comentarios, saltos de líneas y espacios en blanco son ignorados (no
retornan ningún valor).
Otro aspecto importante a destacar, es que las palabras reservadas son tratadas como:
“Identificadores”, esto se debe a que PLY da precedencia a las expresiones regulares
más generales. Por ejemplo, la palabra reservada: “Imprimir”, siempre hará match con
la expresión regular de Identificador, por lo que si se define de la forma: “t_IMPRIMIR
= r'imprimir'”, nunca será alcanzado. Esto lo hace con la finalidad de hacer el proceso
de parsing más eficiente al tener menos expresiones regulares que evaluar.
El objetivo principal de nuestro analizador sintáctico es validar que la entrada sea válida
y, si lo es, construir el AST. Para lograr esto hacemos uso de la programación orientada
a objetos. Específicamente haremos uso del polimorfismo para la construcción de nuestro
árbol. Las clases utilizadas para construir las diferentes instrucciones que componen
nuestro AST, están definidas en el archivo: “instrucciones.py”.
Primero definimos una clase abstracta: “Instruccion”, esto nos permitirá abstraer las
Instrucciones que soporta nuestro lenguaje.
Seguidamente, definimos una clase concreta para cada una de las formas posibles que
puede tomar: “Instrucción”.
Por ejemplo, para la clase: “Imprimir”, vemos que extiende de: “Instruccion”, y que su
única propiedad es la cadena que se va imprimir. Esta propiedad, cadena, es de tipo
“ExpresionCadena”, que se explicará más adelante.
Las formas que puede tomar nuestra clase: “ExpresionNumerica”, son las siguientes:
Imagen 22. Clases abstractas de tipos de expresiones que puede tomar una expresión
numérica.
Imagen 23. Clases abstractas de tipos de expresiones que puede tomar una expresión
de cadena.
Imagen 24. Clases abstractas de tipos de expresiones que puede tomar una expresión
lógica.
Una vez importados, podemos hacer uso de ellas en la gramática. Por ejemplo, para la
construcción de operaciones aritméticas hacemos uso de nuestras clases de tipo:
“ExpresionNumerica”, pasamos como parámetros los operandos y el tipo operación
(utilizando nuestras constantes).
Finalmente, una vez que hayamos reconocido toda la entrada, construimos un arreglo con
cada uno de los nodos. Este será nuestro AST.
La tabla de símbolos.
Se definieron las constantes para los tipos de datos, en este ejemplo hace uso únicamente
del tipo de dato numérico.
Imagen 32. Importando las clases pertinente para el correcto funcionamiento del
programa.
Para iniciar con la ejecución, se crea la tabla de símbolos para el ámbito global y se invoca
la función procesar_instrucciones, con la raíz del AST y la tabla de símbolos del ámbito
global.
Las sentencias Mientras, If e If-Else crean nuevas tablas de símbolos antes de procesar
las instrucciones dentro de sus bloques de instrucciones. Estas nuevas tablas de símbolos
se inicializan con los valores de la tabla de símbolo actual y al terminar la ejecución de la
sentencia los valores son eliminados, ya que, la instancia se crea localmente en el cuerpo
de la función.
Finalmente, todas las sentencias descritas anteriormente hacen uso de las operaciones
numéricas, con cadenas y lógicas las cuales hacen uso de la tabla de símbolos para obtener
valores de las variables.
Para las expresiones numéricas evaluamos el tipo de operación y con base en ellos
resolvemos el valor apropiado.
Para las expresiones con cadenas, también validamos el tipo de operación para verificar
si es necesario una operación de concatenación. En cualquier caso, se resuelve la cadena.
También es posible concatenar valores numéricos, para esto resolvemos la expresión
apoyándonos de la función para procesar expresiones numéricas.
Al igual que las expresiones con cadena, las expresiones lógicas también se apoyan en la
función que procesa expresiones numéricas para poder evaluar las condiciones booleanas.
Pruebas y resultados.
Ejecución.
Para ejecutar el programa, desde el editor de texto que esté utilizando, ejecutar
específicamente el archivo llamado: “gramatica.py”.
Una vez compilado el programa, procedemos a observar los resultados obtenidos, para el
primer ejemplo, podemos ver que primero se hace la lectura del archivo: “entrada.txt”,
posteriormente, se muestran los resultados obtenidos, una vez pasada la prueba del
analizador léxico y sintáctico que se ha realizado en el programa.
Para experimentar con otros valores y verificar que nuestro programa funciona
correctamente, es necesario probar con todas las operaciones posibles, como se mostrará
a continuación.
Como primer paso, se introdujeron en las cuatro primeras líneas, las operaciones básicas,
tales como, sumar, restar, multiplicar y dividir, de igual forma, en la penúltima línea, se
puede visualizar como se describe una operación que aplica jerarquía de operaciones, este
paso será importante en los pasos de la definición de las gramáticas en las funciones, ya
que, determinará su resultado correcto. Finalmente, en la última línea, se introdujo una
prueba de error, para ver si en las funciones sintácticas de nuestro analizador se cumple
con la función que determinamos.
Imagen 41. Expresiones escritas para evaluar en tiempo de compilación del programa.
De forma resumida, los resultados pasan por dos etapas, el analizador léxico y el
analizador sintáctico, en el analizador léxico, se crea la tokenización de los caracteres del
programa (si se va a evaluar, si será un carácter, un tipo de dato, un mensaje de error, un
salto de línea, etc.).
En el analizador sintáctico, trabaja conjuntamente con los tokens que se han creado,
generando la asociatividad y precedencia de los operadores, debido a que la gramática
propuesta en el analizador léxico es ambigua. En esta etapa, se verifica que las
operaciones sean correctas, si tal carácter va a ser aceptado o no, si se realizará alguna
operación, si se enviará por pantalla un mensaje, si se leerá algún archivo, etc.
Ambos análisis van de la mano, para el correcto funcionamiento del programa. De esta
manera, se concluye con la parte de resultados para el primer programa, demostrando en
la prueba de resultados anteriores que su compilamiento fue el correcto.
Ejecución.
Para ejecutar el programa, desde el editor de texto que esté utilizando, ejecutar
específicamente el archivo llamado: “principal.py”.
Una vez compilado el programa, procedemos a observar los resultados obtenidos, para el
primer ejemplo, podemos ver que primero se hace la lectura del archivo: “entrada.txt”,
posteriormente, se muestran los resultados obtenidos, una vez pasada la prueba del
analizador léxico y sintáctico que se ha realizado en el programa.
Conclusiones.
El análisis léxico es una técnica que se encuentra basada en un conjunto de reglas que
relacionan un conjunto de partes para formar un CPU. Un analizador léxico es la primera
fase de un compilador consiste en un programa que recibe el código fuente de otro
programa y produce una salida compuesta en tokens o símbolos. Estos tokens sirven para
una posterior etapa del proceso de traducción, siendo la entrada del analizador sintáctico.
Un lenguaje de programación incluye un conjunto de reglas que definen léxico, las cuales
consisten en expresiones regulares que indican el conjunto de posibles secuencias de
carácter que definen un token o lexema.
Como comentarios finales, los analizadores léxicos y sintácticos son una aplicación de
los compiladores que se encargan de verificar que el texto esté escrito en un formato
aceptado para todo el programa que está escrito en un lenguaje de programación, al igual
que se encarga de verificar que tenga congruencia, los analizadores léxicos y sintácticos
sirven en gran parte para resolver problemas que pueden surgir a causa de que el programa
no tenga congruencia o no este bien estructurado.
Referencias.