├── Tema1 ├── img │ ├── Inversa.png │ ├── Subcadenas.png │ └── PotenciaCadenas.png ├── tema1.md └── Tema1 │ ├── 1_4.ipynb │ ├── 1_3.ipynb │ ├── 1_1.ipynb │ ├── 1_5.ipynb │ └── 1_2.ipynb ├── Tema2 ├── Tema2 │ ├── Datos.txt │ ├── Expresiones_Regulares.txt │ ├── 2_1.md │ ├── 2_2.md │ └── Practica.ipynb └── tema2.md ├── Tema6 ├── tema6.md └── tema6 │ ├── 6_1.md │ ├── 6_2.md │ └── 6_3.md ├── Tema4 ├── tema4 │ ├── 4_5.md │ ├── 4_1.md │ ├── 4_4.md │ ├── 4_3.md │ └── 4_2.md └── tema4.md ├── Tema5 ├── tema5.md └── tema5 │ ├── 5_1.md │ ├── 5_9.md │ ├── 5_7.md │ ├── 5_2.md │ ├── 5_10.md │ ├── 5_5.md │ ├── 5_3.md │ ├── 5_4.md │ ├── 5_6.md │ └── 5_8.md ├── Tema3 ├── tema3.md └── Tema3 │ └── 3_1.md └── Readme.md /Tema1/img/Inversa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RodolfoBaume/LenguajesAutomatas/HEAD/Tema1/img/Inversa.png -------------------------------------------------------------------------------- /Tema1/img/Subcadenas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RodolfoBaume/LenguajesAutomatas/HEAD/Tema1/img/Subcadenas.png -------------------------------------------------------------------------------- /Tema1/img/PotenciaCadenas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RodolfoBaume/LenguajesAutomatas/HEAD/Tema1/img/PotenciaCadenas.png -------------------------------------------------------------------------------- /Tema2/Tema2/Datos.txt: -------------------------------------------------------------------------------- 1 | Carlos Arturo 2 | 449 123 45 67 3 | carlos_@hotmail.com 4 | www.carlos.com 5 | 6 | Manuel Alejandro 7 | 448-234-56-78 8 | alejandro@outlook.com 9 | https://www.manuel.alejandro.com.mx 10 | http://alejandro.com.mx 11 | 12 | Cesar Alan 13 | 449 345 67 89 14 | cesar@hotmail.com 15 | cesar.net -------------------------------------------------------------------------------- /Tema6/tema6.md: -------------------------------------------------------------------------------- 1 | # 6. Máquinas de Turing. 2 | 3 | ## Competencia Específica : 4 | 5 | Diseña y construye o simula una Maquina de Turing (MT), para el reconocimiento de cadenas propias de lenguajes. 6 | 7 | [6.1 Definición formal MT.](tema6/6_1.md) 8 | 9 | [6.2 Construcción modular de una MT.](tema6/6_2.md) 10 | 11 | [6.3 Lenguajes aceptados por la MT.](tema6/6_3.md) 12 | 13 | -------------------------------------------------------------------------------- /Tema4/tema4/4_5.md: -------------------------------------------------------------------------------- 1 | # 4.5 Generadores de analizadores Léxicos 2 | 3 | Existen herramientas que automatizan la generación del analizador léxico a partir de la definición de los patrones de los tokens, como: 4 | 5 | * Lex/Flex (para C/C++) 6 | * ANTLR (para Java, C#, Python, etc.) 7 | 8 | * JavaCC (para Java) 9 | 10 | Estas herramientas toman como entrada la especificación de los tokens y generan el código del analizador léxico, lo que simplifica el desarrollo del compilador. 11 | -------------------------------------------------------------------------------- /Tema1/tema1.md: -------------------------------------------------------------------------------- 1 | # 1. Introducción a la Teoría de Lenguajes Formales. 2 | 3 | ## Competencia Específica : 4 | 5 | Identifica los conceptos de lenguajes formales para comprender las fases de un compilador y traductor. 6 | 7 | [1.1 Alfabeto](Tema1/1_1.ipynb) 8 | 9 | [1.2 Cadenas.](Tema1/1_2.ipynb) 10 | 11 | [1.3 Lenguajes, tipos y herramientas.](Tema1/1_3.ipynb) 12 | 13 | [1.4 Estructura de un traductor](Tema1/1_4.ipynb) 14 | 15 | [1.5 Fases de un compilador](Tema1/1_5.ipynb) 16 | 17 | -------------------------------------------------------------------------------- /Tema2/tema2.md: -------------------------------------------------------------------------------- 1 | 2 | # 2. Expresiones Regulares. 3 | 4 | ## Competencia Específica : 5 | 6 | Crea y reconoce Expresiones Regulares para solucionar problemas del entorno. 7 | --- 8 | 9 | [2.1. Definición formal de una ER.](Tema2/2_1.md) 10 | 11 | [2.2. Diseño de ER.](Tema2/2_1.md) 12 | 13 | 2.3. Aplicaciones en problemas reales. 14 | 15 | [Practicas](Tema2/Practica.ipynb) 16 | 17 | [Datos](Tema2/Datos.txt) 18 | 19 | [Referencia Expresiones Regulares](Tema2/Expresiones_Regulares.txt) 20 | 21 | -------------------------------------------------------------------------------- /Tema5/tema5.md: -------------------------------------------------------------------------------- 1 | # 5. Análisis Sintáctico. 2 | 3 | ## Competencia Específica : 4 | 5 | Construye un analizador sintáctico a partir de un lenguaje de programación. 6 | 7 | 8 | [5.1 Definición y clasificación de gramáticas.](tema5/5_1.md) 9 | 10 | [5.2 Gramáticas Libres de Contexto (GLC).](tema5/5_2.md) 11 | 12 | [5.3 Árboles de derivación.](tema5/5_3.md) 13 | 14 | [5.4 Formas normales de Chomsky.](tema5/5_4.md) 15 | 16 | [5.5 Diagramas de sintaxis](tema5/5_5.md) 17 | 18 | [5.6 Eliminación de la ambigüedad.](tema5/5_6.md) 19 | 20 | [5.7 Tipos de analizadores sintácticos](tema5/5_7.md) 21 | 22 | [5.8 Generación de matriz predictiva (cálculo first y follow)](tema5/5_8.md) 23 | 24 | [5.9 Manejo de errores](tema5/5_9.md) 25 | 26 | [5.10 Generadores de analizadores sintácticos](tema5/5_10.md) 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tema2/Tema2/Expresiones_Regulares.txt: -------------------------------------------------------------------------------- 1 | Coincidencias Basicas 2 | . - Cualquier Caracter, excepto nueva linea 3 | \d - Cualquier Digitos (0-9) 4 | \D - No es un Digito (0-9) 5 | \w - Caracter de Palabra (a-z, A-Z, 0-9, _) 6 | \W - No es un Caracter de Palabra. 7 | \s - Espacios de cualquier tipo. (espacio, tab, nueva linea) 8 | \S - No es un Espacio, Tab o nueva linea. 9 | 10 | Limites 11 | \b - Limite de Palabra 12 | \B - No es un Limite de Palabra 13 | ^ - Inicio de una cadena de texto 14 | $ - Final de una cadena de texto 15 | 16 | Cuantificadores: 17 | * - 0 o Más 18 | + - 1 o Más 19 | ? - 0 o Uno 20 | {3} - Numero Exacto 21 | {3,4} - Rango de Numeros (Minimo, Maximo) 22 | 23 | Conjuntos de Caracteres 24 | [] - Caracteres dentro de los brackets 25 | [^ ] - Caracteres que NO ESTAN dentro de los brackets 26 | 27 | Grupos 28 | ( ) - Grupo 29 | | - Uno u otro -------------------------------------------------------------------------------- /Tema3/tema3.md: -------------------------------------------------------------------------------- 1 | # 3. Autómatas Finitos. 2 | 3 | 4 | ## Competencia Específica : 5 | 6 | Crea y reconoce autómatas finitos en un lenguaje de programación para la solución de un problema. 7 | 8 | 9 | [3.1 Conceptos: Definición y Clasificación de Autómata Finito (AF).](Tema3/3_1.md) 10 | 11 | 3.2 Conversión de un Autómata Finito No Determinista (AFND) a Autómata Finito Determinista (AFD). 12 | 13 | 3.3 Representación de ER usando AFND 14 | 15 | 3.4 Minimización de estados en un AF 16 | 17 | 3.5 Aplicaciones (definición de un caso de estudio) 18 | 19 | Prácticas 20 | 21 | ## Actividades 22 | 23 | | Actividad | Fecha | % | 24 | | --------- | ----------- | - | 25 | | [Tarea 3.1](https://github.com/RodolfoBaume/LenguajesAutomatas/issues/9) | 23 de Abril | 20 | 26 | | [Tarea 3.2](https://github.com/RodolfoBaume/LenguajesAutomatas/issues/10) | 23 de Abril | 25 | 27 | | Examen | 09 de Mayo | 45 | 28 | | Participación y Asistencia | | 10 | 29 | -------------------------------------------------------------------------------- /Tema4/tema4.md: -------------------------------------------------------------------------------- 1 | # 4. Análisis Léxico. 2 | 3 | ## Competencia Específica : 4 | 5 | Construye un analizador léxico a partir de un lenguaje de programación. 6 | 7 | 8 | 9 | [4.1 Funciones del analizador léxico.](tema4/4_1.md) 10 | 11 | [4.2 Componentes léxicos, patrones y lexemas.](tema4/4_2.md) 12 | 13 | [4.3 Creación de Tabla de tokens.](tema4/4_3.md) 14 | 15 | [4.4 Errores léxicos.](tema4/4_4.md) 16 | 17 | [4.5 Generadores de analizadores Léxicos.](tema4/4_5.md) 18 | 19 | 4.6 Aplicaciones (Caso de estudio). 20 | 21 | 22 | Prácticas 23 | 24 | ## Actividades 25 | 26 | | Actividad | Fecha | % | 27 | | --------- | ----- | -- | 28 | | [Tarea 4.1 Tabla de Tokens para Analizador Léxico ](https://github.com/RodolfoBaume/LenguajesAutomatas/issues/11) | 14 de Mayo | 10 | 29 | | [Tarea 4.2 Documentación ER y AF para Analizador Léxico ](https://github.com/RodolfoBaume/LenguajesAutomatas/issues/12) | 14 de Mayo | 15 | 30 | | [Tarea 4.2 Proyecto Analizador Léxico ](https://github.com/RodolfoBaume/LenguajesAutomatas/issues/13) | 16 de mayo | 30 | 31 | | Examen | 17 Mayo | 35 | 32 | | Participación y Asistencia | | 10 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tema4/tema4/4_1.md: -------------------------------------------------------------------------------- 1 | # 4.1 Funciones del analizador léxico 2 | 3 | ## Leer el código fuente de entrada carácter por carácter 4 | 5 | * El analizador léxico es el primer componente que recibe el código fuente de entrada. 6 | * Lee los caracteres del código fuente uno por uno, avanzando a través del texto. 7 | 8 | ## Agrupar los caracteres en unidades léxicas (tokens) 9 | 10 | * Identifica patrones de caracteres que forman los diferentes tipos de tokens. 11 | * Estos tokens pueden ser palabras clave, identificadores, números, operadores, etc. 12 | * Agrupa los caracteres que forman cada token individual. 13 | 14 | ## Eliminar elementos irrelevantes 15 | 16 | * Ignora y elimina elementos como espacios en blanco, tabulaciones, saltos de línea. 17 | * También elimina comentarios presentes en el código fuente. 18 | * Estos elementos no son relevantes para el análisis sintáctico posterior. 19 | 20 | ## Pasar los tokens al analizador sintáctico 21 | 22 | * Una vez identificados y agrupados los tokens, el analizador léxico los envía al siguiente componente del compilador. 23 | * Este siguiente componente es el analizador sintáctico, que se encarga de analizar la estructura gramatical del programa. 24 | * Los tokens proporcionados por el analizador léxico son la entrada para el analizador sintáctico. 25 | 26 | En resumen, el analizador léxico es el primer paso en el proceso de compilación, encargado de leer el código fuente, identificar los tokens y eliminar elementos irrelevantes, para luego pasar esa información al analizador sintáctico. Estas son sus principales funciones. 27 | -------------------------------------------------------------------------------- /Tema4/tema4/4_4.md: -------------------------------------------------------------------------------- 1 | 2 | # 4.4 Errores léxicos 3 | 4 | Los errores léxicos ocurren cuando el analizador léxico se encuentra con una secuencia de caracteres en el código fuente que no coincide con ninguno de los patrones definidos para los tokens válidos del lenguaje. Algunos ejemplos de este tipo de errores son: 5 | 6 | 7 | 8 | 1. **Identificadores que comienzan con un dígito**:Los identificadores válidos en la mayoría de los lenguajes de programación deben comenzar con una letra o un carácter de subrayado, no con un dígito. Si el analizador léxico se encuentra con un identificador que comienza con un dígito, esto se considera un error léxico. 9 | 2. **Caracteres no válidos en un número**: Los números deben estar formados únicamente por dígitos, y opcionalmente un signo o un punto decimal. Si el analizador léxico encuentra caracteres no numéricos dentro de lo que debería ser un literal numérico, se trata de un error léxico. 10 | 3. **Cadenas de texto sin cerrar correctamente**: Las cadenas de texto deben estar delimitadas por un par de comillas (simples o dobles) u otro delimitador definido. Si el analizador léxico encuentra una cadena de texto que no está cerrada correctamente, se considera un error léxico. 11 | 12 | Cuando el analizador léxico detecta este tipo de errores, debe: 13 | 14 | 1. **Informar al usuario**: Notificar al usuario sobre el error léxico encontrado, indicando su ubicación en el código fuente y la naturaleza del error. 15 | 2. **Intentar recuperarse y continuar el análisis**: Opcionalmente, el analizador léxico puede intentar recuperarse del error y continuar procesando el resto del código fuente. Esto puede implicar ignorar la secuencia de caracteres errónea y pasar al siguiente token válido. 16 | 17 | El manejo adecuado de los errores léxicos es crucial para que el compilador o intérprete pueda procesar correctamente el código fuente y proporcionar información útil al usuario sobre los problemas encontrados. 18 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Lenguajes y Autómatas I 2 | 3 | ## **Competencia Específica:** 4 | 5 | Define, diseña y programa las fases del analizador léxico y sintáctico de un traductor o compilador para preámbulo de la construcción de un compilador. 6 | 7 | ## Temas: 8 | 9 | 10 | | Tema | 11 | | ------------------------------------------------------------------------ | 12 | | [Tema 1. Introducción a la Teoría de Lenguajes Formales.](Tema1/tema1.md) | 13 | | [Tema 2. Expresiones Regulares.](Tema2/tema2.md) | 14 | | [Tema 3. Autómatas Finitos.](Tema3/tema3.md) | 15 | | [Tema 4. Análisis Léxico.](Tema4/tema4.md) | 16 | | [Tema 5. Análisis Sintáctico.](Tema5/tema5.md) | 17 | | [Tema 6. Máquinas de Turing.](Tema6/tema6.md) | 18 | 19 | 20 | ## Bibliografia: 21 | 22 | * Aho Alfred V., U. J. (2007). Compiladores. Principios, técnicas y herramientas (2da. ed.). 23 | * Alfonseca Moreno, M. (2006). Compiladores e intérpretes: teoría y práctica (1ra ed.). España: Pearson/Prentice Hall 24 | * Hopcroft John E., M. R. (2002). Introducción a la Teoría de Autómatas, Lenguajes y Computación (2da. ed.). Madrid: Addison-Wesley. 25 | * Carrión Viramontes, J. E. (2008). Teoría de la computación. México: Limusa. 26 | * Isasi Pedro, M. P. (1997). Lenguajes, gramáticas y autómatas. Un enfoque Práctico. 27 | Addison-Wesley. 28 | * Kelley, D. (1995). Teoría de Autómatas y Lenguajes Formales, (1ra. ed.). Madrid: 29 | Prentice Hall. 30 | * Lemone, K. A. (1996). Fundamentos de compiladores: cómo traducir al lenguaje de 31 | computadora. México D.F.: Compañía Editorial Continental. 32 | * Martin, J. (2004). Lenguajes formales y teoría de la computación. México: McGraw-Hill / 33 | Interamericana de México. 34 | * Ruíz, J. (2009). Compiladores-Teoría e implementación. México: Alfaomega. 35 | * Grune, Dick. (2007). Diseño de compiladores modernos. McGraw-Hill. 36 | -------------------------------------------------------------------------------- /Tema4/tema4/4_3.md: -------------------------------------------------------------------------------- 1 | # 4.3 Creación de Tabla de tokens 2 | 3 | Después de que el analizador léxico ha identificado los tokens en el código fuente, se procede a almacenarlos en una tabla de tokens. Esta tabla contiene la siguiente información: 4 | 5 | ## Tipo de token 6 | 7 | * Cada entrada en la tabla de tokens incluye el tipo de token al que pertenece el lexema identificado. Esto puede ser una palabra clave, un identificador, un número, un operador, etc. Esta clasificación es esencial para el análisis posterior del código. 8 | 9 | ## Lexema 10 | 11 | * El lexema es la secuencia de caracteres que forma el token. En la tabla de tokens, se registra el lexema correspondiente a cada token identificado. Esta información es crucial para mantener la integridad y la representación precisa de los componentes léxicos. 12 | 13 | ## Posición en el código fuente 14 | 15 | * Se registra la posición del token en el código fuente. Esta información ayuda a rastrear la ubicación exacta de cada token, lo que puede ser útil para informar errores o para el análisis semántico posterior. 16 | 17 | ## Información adicional 18 | 19 | * Además del tipo de token y el lexema, la tabla de tokens puede contener información adicional relevante para cada token. Por ejemplo, el valor numérico de un número, la longitud de una cadena de texto, u otros detalles específicos que puedan ser necesarios para el análisis posterior. 20 | 21 | ## Paso al analizador sintáctico 22 | 23 | * Una vez que la tabla de tokens se ha creado y poblado con la información necesaria, se pasa al analizador sintáctico como entrada para el siguiente paso del proceso de compilación. El analizador sintáctico utilizará esta tabla para construir la estructura gramatical del programa. 24 | 25 | En resumen, la creación de una tabla de tokens en un analizador léxico es un paso fundamental que organiza y almacena la información clave sobre los tokens identificados en el código fuente. Esta tabla proporciona los datos necesarios para el análisis sintáctico y otros procesos posteriores en el proceso de compilación del programa. 26 | -------------------------------------------------------------------------------- /Tema2/Tema2/2_1.md: -------------------------------------------------------------------------------- 1 | # 2.1 Expresiones Regulares 2 | 3 | La definición formal de una Expresión Regular (ER) es una secuencia de caracteres que define un patrón de búsqueda. Estos patrones son utilizados principalmente en la búsqueda y manipulación de texto. Formalmente, una expresión regular se define como una cadena de símbolos, letras y/o caracteres especiales que representan un conjunto de cadenas posibles que cumplen con ciertas condiciones. 4 | 5 | Una expresión regular puede incluir: 6 | 7 | 1. **Caracteres literales:** Representan ellos mismos y no tienen un significado especial. Por ejemplo, la expresión regular "hola" coincide exactamente con la cadena "hola". 8 | 9 | 2. **Metacaracteres:** Son caracteres especiales que tienen un significado especial dentro de una expresión regular. Algunos ejemplos comunes de metacaracteres incluyen: 10 | - `.`: Coincide con cualquier carácter excepto un salto de línea. 11 | - `^`: Coincide con el inicio de una cadena. 12 | - `$`: Coincide con el final de una cadena. 13 | - `\`: Se utiliza para escapar metacaracteres y tratarlos como caracteres literales. 14 | - `[]`: Define un conjunto de caracteres. 15 | - `|`: Se utiliza para alternar entre diferentes patrones. 16 | - `()`: Agrupa subexpresiones. 17 | 18 | 3. **Cuantificadores:** Se utilizan para especificar cuántas veces debe repetirse un elemento en una cadena. 19 | - `*`: Coincide con cero o más repeticiones del elemento anterior. 20 | - `+`: Coincide con una o más repeticiones del elemento anterior. 21 | - `?`: Coincide con cero o una repetición del elemento anterior. 22 | - `{n}`: Coincide exactamente con n repeticiones del elemento anterior. 23 | - `{n, m}`: Coincide con al menos n y como máximo m repeticiones del elemento anterior. 24 | 25 | Por ejemplo, la expresión regular `a+` coincidirá con una o más repeticiones del carácter "a". La expresión regular `^[0-9]{3}-[0-9]{3}-[0-9]{4}$` coincidirá con un número de teléfono en formato ###-###-####. 26 | 27 | En resumen, la definición formal de una expresión regular consiste en un conjunto de reglas y símbolos que representan un patrón de búsqueda dentro de un texto. Estas expresiones regulares se utilizan ampliamente en la búsqueda y manipulación de cadenas de texto en diversos contextos, como la programación, la manipulación de archivos de texto y el procesamiento de datos. 28 | -------------------------------------------------------------------------------- /Tema2/Tema2/2_2.md: -------------------------------------------------------------------------------- 1 | # 2.2 Diseño de una Expresión Regular 2 | 3 | En el diseño de expresiones regulares (ER) se busca crear patrones que permitan identificar ciertos tipos de cadenas de texto de interés. Aquí hay algunas definiciones útiles para explicar el proceso de diseño de ER: 4 | 5 | 1. **Patrón de búsqueda:** Es la estructura o conjunto de reglas que define qué tipo de cadenas de texto estamos tratando de encontrar o validar. Este patrón puede incluir caracteres literales, metacaracteres y cuantificadores. 6 | 7 | 2. **Caracteres literales:** Son los caracteres que representan ellos mismos en una expresión regular. Por ejemplo, si buscamos el patrón "hola", estamos buscando la cadena exacta "hola". 8 | 9 | 3. **Metacaracteres:** Son caracteres especiales que tienen un significado especial dentro de una expresión regular. Por ejemplo: 10 | - `.`: Coincide con cualquier carácter excepto un salto de línea. 11 | - `^`: Coincide con el inicio de una cadena. 12 | - `$`: Coincide con el final de una cadena. 13 | - `\`: Se utiliza para escapar metacaracteres y tratarlos como caracteres literales. 14 | - `[]`: Define un conjunto de caracteres. 15 | - `|`: Se utiliza para alternar entre diferentes patrones. 16 | - `()`: Agrupa subexpresiones. 17 | 18 | 4. **Cuantificadores:** Son símbolos que se utilizan para especificar cuántas veces debe repetirse un elemento en una cadena. 19 | - `*`: Coincide con cero o más repeticiones del elemento anterior. 20 | - `+`: Coincide con una o más repeticiones del elemento anterior. 21 | - `?`: Coincide con cero o una repetición del elemento anterior. 22 | - `{n}`: Coincide exactamente con n repeticiones del elemento anterior. 23 | - `{n, m}`: Coincide con al menos n y como máximo m repeticiones del elemento anterior. 24 | 25 | 5. **Conjunto de caracteres:** Se utiliza `[ ]` para definir un conjunto de caracteres que pueden coincidir en una posición determinada. Por ejemplo, `[aeiou]` coincidirá con cualquier vocal en minúscula. 26 | 27 | 6. **Agrupación de subexpresiones:** Los paréntesis `( )` se utilizan para agrupar partes de una expresión regular. Esto permite aplicar cuantificadores o metacaracteres a una subexpresión completa. 28 | 29 | Al diseñar una expresión regular, es importante considerar la estructura y el formato de las cadenas de texto que se están buscando y utilizar los metacaracteres, cuantificadores y conjuntos de caracteres adecuados para definir un patrón de búsqueda efectivo. También es útil probar la expresión regular con diferentes ejemplos de cadenas de texto para asegurarse de que funcione como se espera en una variedad de situaciones. -------------------------------------------------------------------------------- /Tema5/tema5/5_1.md: -------------------------------------------------------------------------------- 1 | # 5.1 Definición y clasificación de gramáticas 2 | 3 | Las gramáticas son herramientas formales que se utilizan para describir la estructura sintáctica de un lenguaje. Consisten en un conjunto de reglas que especifican cómo se pueden combinar diferentes elementos del lenguaje para formar expresiones válidas. Las gramáticas se utilizan en diversos campos, como la lingüística computacional, la teoría de la computación y la compilación de lenguajes de programación. 4 | 5 | ## Componentes de una gramática: 6 | 7 | 1. **Alfabeto** : Es el conjunto finito de símbolos que se pueden utilizar para construir expresiones en el lenguaje. Estos símbolos pueden ser tanto terminales (símbolos que aparecen en la cadena final) como no terminales (símbolos que se utilizan en las reglas de producción pero que no aparecen directamente en la cadena final). 8 | 2. **Reglas de producción** : Son las reglas que especifican cómo se pueden combinar los símbolos de la gramática para formar expresiones válidas en el lenguaje. Cada regla consta de un símbolo no terminal en el lado izquierdo y una secuencia de símbolos (terminales y/o no terminales) en el lado derecho. 9 | 10 | ## Clasificación de gramáticas: 11 | 12 | 1. **Gramáticas regulares** : Son las gramáticas más simples y restrictivas. Se caracterizan por tener reglas de producción de la forma A -> aB o A -> a, donde "a" es un terminal, "B" es un no terminal y "A" es un no terminal inicial. Las gramáticas regulares se utilizan principalmente para describir lenguajes regulares, como expresiones regulares. 13 | 2. **Gramáticas libres de contexto (GLC)** : Son un tipo más poderoso de gramática que las regulares. En una GLC, las reglas de producción son de la forma A -> α, donde "A" es un no terminal y "α" es una cadena de terminales y/o no terminales. Las GLC se utilizan en la descripción de lenguajes formales y son la base de muchos analizadores sintácticos. 14 | 3. **Gramáticas sensibles al contexto** : Son aún más poderosas que las GLC. Permiten que las reglas de producción se adapten al contexto en el que aparecen los símbolos. Sin embargo, son menos comunes en la práctica debido a su complejidad. 15 | 4. **Gramáticas recursivas** : Son aquellas en las que una regla de producción puede hacer referencia a sí misma directa o indirectamente. Estas gramáticas son especialmente útiles para describir estructuras recursivas, como las presentes en muchos lenguajes de programación. 16 | 5. **Gramáticas sin restricciones** : Son el tipo más general de gramática. No imponen ninguna restricción sobre las reglas de producción y pueden describir cualquier lenguaje formal. Sin embargo, son demasiado poderosas para la mayoría de las aplicaciones prácticas y su uso está más limitado a la teoría de la computación. 17 | 18 | Comprender los diferentes tipos de gramáticas y sus características es fundamental para el diseño y análisis de lenguajes formales, así como para la implementación de analizadores sintácticos y compiladores. Cada tipo de gramática tiene sus propias propiedades y capacidades, lo que las hace adecuadas para diferentes aplicaciones y contextos. 19 | -------------------------------------------------------------------------------- /Tema5/tema5/5_9.md: -------------------------------------------------------------------------------- 1 | # Manejo de errores en el análisis sintáctico 2 | 3 | El manejo de errores es un aspecto crítico en el diseño e implementación de analizadores sintácticos para garantizar la robustez y usabilidad de un compilador o sistema de procesamiento de lenguajes. Aquí están algunos puntos importantes a considerar: 4 | 5 | ## 1. Identificación de errores: 6 | 7 | Es crucial tener un mecanismo para detectar y identificar errores sintácticos durante el análisis. Los errores pueden incluir tokens inesperados, falta de tokens esperados, y estructuras sintácticas inválidas. 8 | 9 | ## 2. Estrategias de recuperación: 10 | 11 | Una vez que se detecta un error, es importante recuperarse de manera adecuada para continuar con el análisis sintáctico y proporcionar mensajes de error útiles y comprensibles para el usuario. Las estrategias comunes de recuperación de errores incluyen: 12 | 13 | * **Pánico o Modo de Símbolo de Sincronización** : El analizador intenta recuperarse sincronizándose con la entrada buscando un símbolo de sincronización (como un punto y coma o una llave de cierre) antes de continuar el análisis. 14 | * **Inserción o eliminación de tokens** : El analizador puede insertar o eliminar tokens en la entrada para corregir el error y continuar el análisis. Por ejemplo, puede insertar un token faltante o eliminar un token adicional. 15 | * **Reinicio parcial o total** : En casos extremos, el analizador puede reiniciarse parcial o totalmente para intentar recuperarse del error. Esto puede implicar descartar parte del análisis previo y volver a iniciar desde un punto conocido. 16 | 17 | ## 3. Mensajes de error claros: 18 | 19 | Es esencial proporcionar mensajes de error claros y útiles para ayudar al usuario a comprender y corregir los problemas en el código fuente. Los mensajes de error deben indicar la ubicación del error, una descripción del problema y sugerencias para corregirlo. 20 | 21 | ## 4. Detección de errores en tiempo real: 22 | 23 | En entornos interactivos, como editores de código, es útil detectar y señalar errores sintácticos en tiempo real mientras el usuario escribe el código. Esto ayuda a los programadores a corregir errores de manera proactiva antes de compilar o ejecutar el código. 24 | 25 | ## 5. Pruebas exhaustivas: 26 | 27 | Es importante realizar pruebas exhaustivas para garantizar que el analizador sintáctico maneje correctamente una variedad de casos de prueba, incluidos casos de entrada válidos e inválidos. Las pruebas deben cubrir diferentes escenarios y situaciones de error para garantizar la robustez del sistema. 28 | 29 | ## Importancia del manejo de errores: 30 | 31 | * Mejora la usabilidad y experiencia del usuario al proporcionar mensajes de error claros y sugerencias de corrección. 32 | * Aumenta la robustez y confiabilidad del sistema al garantizar que pueda manejar errores sintácticos de manera adecuada y recuperarse de ellos. 33 | * Contribuye a la calidad del software al detectar y corregir errores durante el desarrollo y las pruebas del compilador o sistema de procesamiento de lenguajes. 34 | 35 | En resumen, el manejo de errores en el análisis sintáctico es un aspecto fundamental del diseño e implementación de compiladores y sistemas de procesamiento de lenguajes. Proporciona la capacidad de detectar, identificar y recuperarse de manera adecuada de los errores sintácticos, lo que mejora la usabilidad, robustez y confiabilidad del sistema. 36 | -------------------------------------------------------------------------------- /Tema5/tema5/5_7.md: -------------------------------------------------------------------------------- 1 | # 5.7 Tipos de analizadores sintácticos 2 | 3 | Los analizadores sintácticos son herramientas utilizadas en el proceso de análisis de la estructura sintáctica de las cadenas de un lenguaje de programación o un lenguaje formal. Hay varios tipos de analizadores sintácticos, cada uno con sus propias características y técnicas de análisis. Los tipos más comunes son: 4 | 5 | ## 1. Analizadores sintácticos descendentes (Top-Down): 6 | 7 | Estos analizadores comienzan desde el símbolo inicial de la gramática y tratan de construir la cadena de entrada descendiendo hacia abajo en el árbol de derivación. Utilizan reglas de producción para reescribir el símbolo inicial en términos de símbolos terminales y no terminales. Los analizadores LL(k) y Recursive Descent son ejemplos de analizadores sintácticos descendentes. 8 | 9 | ## 2. Analizadores sintácticos ascendentes (Bottom-Up): 10 | 11 | Estos analizadores comienzan desde los símbolos terminales de la cadena de entrada y tratan de construir el símbolo inicial de la gramática ascendiendo hacia arriba en el árbol de derivación. Utilizan reglas de producción para reducir la cadena de entrada a medida que encuentran símbolos no terminales. Los analizadores LR(k) y LALR son ejemplos de analizadores sintácticos ascendentes. 12 | 13 | ## 3. Analizadores sintácticos predictivos: 14 | 15 | Estos analizadores utilizan predicciones basadas en un conjunto de reglas de producción y un número fijo de símbolos de anticipación (lookahead) para determinar qué regla de producción aplicar en cada paso del análisis. Son analizadores sintácticos descendentes no recursivos. Los analizadores LL(k) son un tipo común de analizadores sintácticos predictivos. 16 | 17 | ## 4. Analizadores sintácticos de tabla: 18 | 19 | Estos analizadores construyen una tabla de análisis que codifica las decisiones de análisis sintáctico para cada combinación de símbolos de entrada y símbolos de pila. Las tablas de análisis pueden ser generadas automáticamente a partir de la gramática y se utilizan para determinar las acciones de análisis (desplazamiento, reducción, aceptación, etc.) en cada paso del análisis sintáctico. Los analizadores LR(k) y LALR son ejemplos de analizadores sintácticos de tabla. 20 | 21 | ## 5. Analizadores sintácticos recursivos: 22 | 23 | Estos analizadores utilizan funciones recursivas para realizar el análisis sintáctico. Cada función recursiva corresponde a un símbolo no terminal en la gramática y se encarga de analizar ese símbolo y sus producciones. Los analizadores sintácticos descendentes recursivos son un ejemplo común de analizadores sintácticos recursivos. 24 | 25 | ## Elección del tipo de analizador sintáctico: 26 | 27 | La elección del tipo de analizador sintáctico depende de varios factores, como la complejidad de la gramática, los requisitos de rendimiento, la facilidad de implementación y mantenimiento, y las características del lenguaje de programación. Cada tipo de analizador sintáctico tiene sus propias ventajas y desventajas, y es importante seleccionar el tipo adecuado para una aplicación específica. 28 | 29 | En resumen, existen varios tipos de analizadores sintácticos, cada uno con sus propias técnicas y enfoques para el análisis de la estructura sintáctica de las cadenas de un lenguaje. La elección del tipo de analizador sintáctico depende de diversos factores y es fundamental para el diseño y la implementación de compiladores y otros sistemas de procesamiento de lenguajes formales. 30 | -------------------------------------------------------------------------------- /Tema5/tema5/5_2.md: -------------------------------------------------------------------------------- 1 | # 5.2 Gramáticas Libres de Contexto (GLC) 2 | 3 | Las Gramáticas Libres de Contexto (GLC) son un tipo de gramática formal en la que las reglas de producción tienen una forma específica y relativamente simple. Son ampliamente utilizadas en la teoría de la computación, la lingüística computacional, la compilación de lenguajes de programación y otros campos relacionados. Las GLC son más poderosas que las gramáticas regulares y ofrecen una representación más flexible de la estructura sintáctica de un lenguaje. 4 | 5 | ## Componentes de una GLC: 6 | 7 | 1. **Conjunto de símbolos terminales (Σ)** : Es un conjunto finito de símbolos que representan los elementos básicos del lenguaje, es decir, los símbolos que aparecen directamente en la cadena final generada por la gramática. 8 | 2. **Conjunto de símbolos no terminales (N)** : Es un conjunto finito de símbolos que representan las categorías o partes de discurso del lenguaje. Los símbolos no terminales se utilizan en las reglas de producción para especificar cómo se pueden combinar los elementos del lenguaje para formar expresiones válidas. 9 | 3. **Símbolo inicial (S)** : Es un símbolo no terminal que representa el punto de partida para la generación de cadenas en el lenguaje. La generación de cadenas comienza con el símbolo inicial y se aplica repetidamente las reglas de producción hasta que se alcanzan los símbolos terminales. 10 | 4. **Conjunto de reglas de producción (P)** : Son reglas que especifican cómo se pueden combinar los símbolos no terminales y terminales para formar expresiones válidas en el lenguaje. Cada regla tiene la forma A -> α, donde "A" es un símbolo no terminal y "α" es una cadena de símbolos (terminales y/o no terminales). 11 | 12 | ## Ejemplo de una GLC: 13 | 14 | Consideremos la siguiente gramática libre de contexto como ejemplo: 15 | 16 | N = {S, A} 17 | 18 | En esta gramática, los símbolos no terminales son "S" y "A", los símbolos terminales son "a" y "b", y el símbolo inicial es "S". Las reglas de producción especifican cómo se pueden combinar los símbolos para generar cadenas en el lenguaje. Por ejemplo, la regla "S -> aSb | A" indica que la cadena puede comenzar con "a", seguido de cualquier cadena generada por "S", seguido de "b", o bien, puede ser simplemente "A". 19 | 20 | ## Uso de GLC: 21 | 22 | Las GLC se utilizan en una variedad de aplicaciones en informática y lingüística computacional, incluyendo: 23 | 24 | * **Análisis sintáctico** : Las GLC se utilizan para definir la sintaxis de los lenguajes de programación y otros lenguajes formales. Los analizadores sintácticos utilizan estas gramáticas para analizar la estructura sintáctica de las cadenas de entrada y determinar si son gramaticalmente correctas. 25 | * **Compiladores** : En la fase de análisis de un compilador, se utiliza un analizador sintáctico para verificar la validez de la estructura del código fuente de un programa según la gramática del lenguaje. Si el código fuente no cumple con la gramática definida, se generan errores de sintaxis. 26 | * **Procesamiento de lenguaje natural (PLN)** : En PLN, las GLC se utilizan para modelar la sintaxis de los idiomas naturales. Estas gramáticas pueden utilizarse para analizar y generar frases gramaticalmente correctas en un idioma. 27 | 28 | En resumen, las Gramáticas Libres de Contexto proporcionan una forma formal y poderosa de describir la estructura sintáctica de un lenguaje, y son fundamentales en muchos campos de la informática y la lingüística computacional. 29 | -------------------------------------------------------------------------------- /Tema4/tema4/4_2.md: -------------------------------------------------------------------------------- 1 | # 4.2 Componentes léxicos, patrones y lexemas 2 | 3 | En el contexto del análisis léxico, los componentes léxicos, patrones y lexemas se relacionan de la siguiente manera: 4 | 5 | **Componentes léxicos**: Son las unidades básicas reconocidas por el analizador léxico. Incluyen palabras clave (if, while, return), identificadores (nombres de variables, funciones), literales (números, cadenas de texto), operadores (=, +, -, *, /) y signos de puntuación (, ; { } ( )). Estos componentes son esenciales para la comprensión del código fuente y se utilizan para construir la estructura del programa. 6 | 7 | **Patrones regulares**: Los componentes léxicos se definen mediante patrones regulares, que son expresiones que describen la estructura de cada tipo de token. Por ejemplo, un identificador podría definirse como una secuencia de letras, dígitos y guiones bajos, que comience con una letra. Estos patrones son fundamentales para identificar y clasificar los lexemas en tokens específicos. 8 | 9 | **Lexema**: Es la secuencia de caracteres en el programa fuente que coincide con un patrón definido para un token específico. El lexema es la instancia concreta de un token y representa la información concreta que se reconoce en el código fuente. Por ejemplo, la secuencia de caracteres "if" en el código fuente coincidiría con el lexema de una palabra clave "if". 10 | 11 | En resumen, los componentes léxicos son las unidades básicas reconocidas por el analizador léxico, definidas mediante patrones regulares que describen su estructura. El lexema es la secuencia de caracteres que coincide con un patrón y forma un token específico, permitiendo al analizador léxico identificar y clasificar los elementos del código fuente de manera precisa. 12 | 13 | 14 | ## Cómo se relacionan los tokens con los lexemas en un analizador léxico 15 | 16 | En un analizador léxico, los tokens y los lexemas están estrechamente relacionados. Aquí se explica cómo se relacionan:* **Token**: Un token es una unidad básica reconocida por el analizador léxico y consiste en un nombre de token y, opcionalmente, un valor de atributo. El nombre del token es un símbolo abstracto que representa un tipo de unidad léxica, como una palabra clave, un identificador, un número, etc. 17 | 18 | * **Lexema**: Un lexema es una secuencia de caracteres en el programa fuente que coincide con el patrón para un token. Es decir, el lexema es la parte del código fuente que el analizador léxico identifica como una instancia de un token específico. 19 | 20 | La relación entre tokens y lexemas se establece de la siguiente manera: 21 | 22 | 1. **Identificación del lexema**: El analizador léxico lee el código fuente carácter por carácter y agrupa los caracteres en lexemas que coinciden con los patrones definidos para cada tipo de token. 23 | 2. **Asignación del token**: Una vez identificado un lexema que coincide con un patrón de token, se asigna a ese lexema el token correspondiente, que incluye su nombre y, en algunos casos, un valor de atributo. 24 | 3. **Representación del token**: El token representa el tipo de componente léxico al que pertenece el lexema. Por lo tanto, cada lexema identificado se asocia con un token específico que lo clasifica dentro del lenguaje de programación. 25 | 26 | En resumen, en un analizador léxico, los lexemas son las secuencias de caracteres en el código fuente que coinciden con los patrones de los tokens, y los tokens son las unidades reconocidas que representan los diferentes componentes léxicos del lenguaje de programación. La relación entre ellos es fundamental para el proceso de análisis léxico en la compilación de programas. 27 | -------------------------------------------------------------------------------- /Tema1/Tema1/1_4.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 4 Estructura de un traductor\n", 8 | "\n", 9 | "La función de un traductor es convertir el código fuente de un lenguaje de programación a un formato que pueda ser entendido y ejecutado por una máquina. Los traductores pueden ser compiladores o intérpretes, y son de suma importancia en la compilación y ejecución de programas, ya que permiten que los programas escritos en lenguajes de alto nivel sean convertidos a lenguaje de máquina comprensible por el hardware.\n", 10 | "\n", 11 | "Los compiladores transforman todo el programa fuente a lenguaje máquina en un solo paso antes de la ejecución, generando un archivo ejecutable. Los intérpretes, por otro lado, traducen y ejecutan el código fuente línea por línea en tiempo real.\n", 12 | "\n", 13 | "Ejemplo en Python: Traducción Simple Utilizando Diccionarios\n", 14 | "\n", 15 | "A continuación, se ilustra un ejemplo simple de traducción utilizando diccionarios en Python. Supongamos que queremos traducir algunas palabras del español al inglés. Podemos utilizar un diccionario para llevar a cabo esta traducción de manera sencilla." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "name": "stdout", 25 | "output_type": "stream", 26 | "text": [ 27 | "La traducción de 'hola' es 'hello'.\n" 28 | ] 29 | } 30 | ], 31 | "source": [ 32 | "# Diccionario de traducción del español al inglés\n", 33 | "diccionario_traduccion = {\n", 34 | " \"hola\": \"hello\",\n", 35 | " \"adiós\": \"goodbye\",\n", 36 | " \"casa\": \"house\",\n", 37 | " \"perro\": \"dog\",\n", 38 | " # Agrega más palabras y sus traducciones si es necesario\n", 39 | "}\n", 40 | "\n", 41 | "# Función para realizar la traducción\n", 42 | "def traducir_palabra(palabra, diccionario):\n", 43 | " if palabra in diccionario:\n", 44 | " return diccionario[palabra]\n", 45 | " else:\n", 46 | " return \"Palabra no encontrada en el diccionario de traducción.\"\n", 47 | "\n", 48 | "# Ejemplo de uso de la función de traducción\n", 49 | "palabra_a_traducir = \"hola\"\n", 50 | "traduccion = traducir_palabra(palabra_a_traducir, diccionario_traduccion)\n", 51 | "print(f\"La traducción de '{palabra_a_traducir}' es '{traduccion}'.\")\n" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "En este ejemplo, se utiliza un diccionario en Python para mapear palabras en español a sus respectivas traducciones en inglés. La función `traducir_palabra` toma una palabra como entrada y busca su traducción en el diccionario. Si la traducción existe, la devuelve; de lo contrario, indica que la palabra no se encuentra en el diccionario.\n", 59 | "\n", 60 | "Este ejemplo muestra cómo utilizar un diccionario en Python para llevar a cabo una traducción simple. Aunque este ejemplo es básico, ilustra el concepto de traducción mediante el uso de estructuras de datos en Python." 61 | ] 62 | } 63 | ], 64 | "metadata": { 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "codemirror_mode": { 72 | "name": "ipython", 73 | "version": 3 74 | }, 75 | "file_extension": ".py", 76 | "mimetype": "text/x-python", 77 | "name": "python", 78 | "nbconvert_exporter": "python", 79 | "pygments_lexer": "ipython3", 80 | "version": "3.10.11" 81 | } 82 | }, 83 | "nbformat": 4, 84 | "nbformat_minor": 2 85 | } 86 | -------------------------------------------------------------------------------- /Tema5/tema5/5_10.md: -------------------------------------------------------------------------------- 1 | # Generadores de analizadores sintácticos 2 | 3 | Los generadores de analizadores sintácticos son herramientas que automatizan el proceso de creación de analizadores sintácticos a partir de especificaciones de gramáticas formales. Estas herramientas permiten a los desarrolladores generar código fuente de analizadores sintácticos de manera eficiente y precisa, lo que simplifica el desarrollo de compiladores y sistemas de procesamiento de lenguajes. 4 | 5 | ## Funcionamiento de los generadores de analizadores sintácticos: 6 | 7 | 1. **Entrada de la gramática** : El usuario proporciona una especificación de la gramática del lenguaje de programación o lenguaje formal utilizando una notación específica. Esta gramática puede estar en forma de gramática de Backus-Naur (BNF), gramática extendida de Backus-Naur (EBNF), o en otro formato compatible con el generador. 8 | 2. **Análisis de la gramática** : El generador de analizadores sintácticos analiza la gramática proporcionada para identificar los símbolos no terminales, los símbolos terminales, las reglas de producción y otras características necesarias para generar el analizador sintáctico. 9 | 3. **Generación de código fuente** : Con base en la gramática analizada, el generador produce código fuente de un analizador sintáctico en un lenguaje de programación específico, como C, C++, Java, o Python. El código generado incluye estructuras de datos, funciones y algoritmos necesarios para realizar el análisis sintáctico del lenguaje. 10 | 4. **Compilación e integración** : El código fuente generado se compila y se integra en el proyecto de desarrollo del compilador o sistema de procesamiento de lenguajes. Dependiendo del generador y del entorno de desarrollo, este paso puede ser automatizado o requerir intervención manual por parte del usuario. 11 | 12 | ## Ejemplos de generadores de analizadores sintácticos: 13 | 14 | 1. **ANTLR (ANother Tool for Language Recognition)** : ANTLR es un popular generador de analizadores sintácticos que admite múltiples lenguajes de programación, incluidos Java, C#, Python y JavaScript. Permite definir gramáticas mediante una notación similar a EBNF y genera analizadores sintácticos eficientes y de alto rendimiento. 15 | 2. **Yacc (Yet Another Compiler Compiler)** : Yacc es una herramienta de generación de analizadores sintácticos ampliamente utilizada en sistemas Unix. Toma una gramática en forma de BNF y genera un analizador sintáctico en C. Bison es una versión de Yacc compatible con GNU. 16 | 3. **PLY (Python Lex-Yacc)** : PLY es una implementación en Python de Lex (analizador léxico) y Yacc (generador de analizadores sintácticos). Permite definir gramáticas directamente en código Python y genera analizadores sintácticos eficientes para procesar lenguajes de programación. 17 | 18 | ## Ventajas de los generadores de analizadores sintácticos: 19 | 20 | * **Productividad** : Automatizan el proceso de creación de analizadores sintácticos, lo que ahorra tiempo y esfuerzo en el desarrollo de compiladores y sistemas de procesamiento de lenguajes. 21 | * **Precisión** : Generan analizadores sintácticos que están libres de errores de implementación y cumplen con las especificaciones de la gramática proporcionada por el usuario. 22 | * **Flexibilidad** : Permiten definir gramáticas de manera flexible y expresiva, lo que facilita la creación de analizadores sintácticos para una amplia variedad de lenguajes de programación y lenguajes formales. 23 | 24 | En resumen, los generadores de analizadores sintácticos son herramientas poderosas que simplifican el desarrollo de compiladores y sistemas de procesamiento de lenguajes al automatizar la creación de analizadores sintácticos a partir de especificaciones de gramáticas formales. Permiten a los desarrolladores generar código fuente de analizadores sintácticos de manera eficiente y precisa, lo que mejora la productividad y la calidad del software generado. 25 | -------------------------------------------------------------------------------- /Tema1/Tema1/1_3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 1.3 Lenguajes, tipos y herramientas.\n", 8 | "\n", 9 | "En el ámbito de la teoría de lenguajes formales, es crucial comprender los diferentes tipos de lenguajes formales y las herramientas que se utilizan para trabajar con ellos. A continuación, se presentarán brevemente los tipos de lenguajes formales y se mostrará cómo utilizar expresiones regulares en Python para validar cadenas dentro de un determinado lenguaje.\n", 10 | "\n", 11 | "### Tipos de Lenguajes Formales:\n", 12 | "\n", 13 | "1. Lenguajes Regulares: Estos lenguajes pueden ser descritos por medio de expresiones regulares y pueden ser reconocidos por autómatas finitos.\n", 14 | "\n", 15 | "2. Lenguajes Libres de Contexto: Estos lenguajes son descritos por medio de gramáticas libres de contexto y pueden ser reconocidos por autómatas de pila.\n", 16 | "\n", 17 | "### Herramientas para Trabajar con Lenguajes Formales:\n", 18 | "\n", 19 | "- Expresiones Regulares: Patrones que describen conjuntos de cadenas de caracteres. En Python, el módulo \"re\" proporciona herramientas para trabajar con expresiones regulares.\n", 20 | "\n", 21 | "- Gramáticas Formales: Descripciones formales de la estructura de un lenguaje, comúnmente utilizadas para definir lenguajes libres de contexto. En Python, se pueden implementar gramáticas formales con herramientas como PLY (Python Lex-Yacc).\n", 22 | "\n", 23 | "- Autómatas: Modelos matemáticos de cómputo que son utilizados para reconocer lenguajes regulares y libres de contexto. En Python, es posible implementar autómatas utilizando programación orientada a objetos u otras técnicas.\n", 24 | "\n", 25 | "Ejemplo en Python: Utilización de Expresiones Regulares para Validar Cadenas Dentro de un Lenguaje Dado" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 4, 31 | "metadata": {}, 32 | "outputs": [ 33 | { 34 | "name": "stdout", 35 | "output_type": "stream", 36 | "text": [ 37 | "La cadena \"ejemplo@dominio.com\" es válida dentro del lenguaje definido.\n" 38 | ] 39 | } 40 | ], 41 | "source": [ 42 | "import re\n", 43 | "\n", 44 | "# Definición de una expresión regular para validar un patrón específico\n", 45 | "patron_validacion = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' # Ejemplo: Validación de un correo electrónico\n", 46 | "\n", 47 | "# Función que utiliza la expresión regular para validar una cadena\n", 48 | "def validar_cadena_con_expresion_regular(cadena, patron):\n", 49 | " if re.match(patron, cadena):\n", 50 | " return True\n", 51 | " return False\n", 52 | "\n", 53 | "# Ejemplo de validación de una cadena utilizando la expresión regular\n", 54 | "cadena_ejemplo = 'ejemplo@dominio.com'\n", 55 | "if validar_cadena_con_expresion_regular(cadena_ejemplo, patron_validacion):\n", 56 | " print(f'La cadena \"{cadena_ejemplo}\" es válida dentro del lenguaje definido.')\n", 57 | "else:\n", 58 | " print(f'La cadena \"{cadena_ejemplo}\" no es válida dentro del lenguaje definido.')" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "En este ejemplo, se utiliza el módulo \"re\" de Python para definir una expresión regular que valida un patrón específico, en este caso, un formato de dirección de correo electrónico. La función `validar_cadena_con_expresion_regular` utiliza esta expresión regular para validar si una cadena cumple con el patrón establecido." 66 | ] 67 | } 68 | ], 69 | "metadata": { 70 | "kernelspec": { 71 | "display_name": "Python 3", 72 | "language": "python", 73 | "name": "python3" 74 | }, 75 | "language_info": { 76 | "codemirror_mode": { 77 | "name": "ipython", 78 | "version": 3 79 | }, 80 | "file_extension": ".py", 81 | "mimetype": "text/x-python", 82 | "name": "python", 83 | "nbconvert_exporter": "python", 84 | "pygments_lexer": "ipython3", 85 | "version": "3.10.11" 86 | } 87 | }, 88 | "nbformat": 4, 89 | "nbformat_minor": 2 90 | } 91 | -------------------------------------------------------------------------------- /Tema6/tema6/6_1.md: -------------------------------------------------------------------------------- 1 | # 6.1 Definición formal de Máquinas de Turing 2 | 3 | Una Máquina de Turing (MT) es un modelo matemático de computación que define una hipotética máquina que manipula símbolos en una cinta de acuerdo con un conjunto de reglas. Es uno de los modelos más fundamentales para estudiar lo que es computable. 4 | 5 | Una Máquina de Turing se define como una 7-tupla 𝑀=(𝑄,Σ,Γ,𝛿,𝑞0,𝑞acept,𝑞rech). Vamos a ver qué representa cada uno de estos componentes: 6 | 7 | 1. **Conjunto de estados (𝑄**) : 8 | 9 | * 𝑄 es un conjunto finito de estados en los que puede estar la MT en cualquier momento. 10 | * Ejemplo: 𝑄={𝑞0,𝑞1,𝑞2,𝑞acept,𝑞rech}**, donde cada 𝑞𝑖** es un estado específico. 11 | 12 | 2. **Alfabeto de entrada (Σ**): 13 | 14 | * **Σ** es un conjunto finito de símbolos que forman el alfabeto de entrada. 15 | * **Σ** no incluye el símbolo en blanco. 16 | * Ejemplo: **Σ**=**{**0**,**1**}** para una MT que procesa cadenas binarias. 17 | 18 | 3. **Alfabeto de la cinta (Γ**) : 19 | 20 | * **Γ** es un conjunto finito de símbolos que pueden escribirse en la cinta. Incluye todos los símbolos de **Σ** y el símbolo en blanco (**⊔**). 21 | * Ejemplo: **Γ**=**{**0**,**1**,**⊔**}**. 22 | 23 | 4. **Función de transición (𝛿**): 24 | 25 | * **δ** es una función de transición que define las reglas de la MT. 26 | * **δ**:**Q**×**Γ**→**Q**×**Γ**×**{**L**,**R**}**. 27 | * Esta función toma el estado actual y el símbolo en la cinta y devuelve un nuevo estado, un símbolo a escribir y una dirección para mover el cabezal (izquierda **L** o derecha **R**). 28 | * Ejemplo: Si 𝛿(𝑞0,1)=(𝑞1,0,𝑅), esto significa que si la MT está en el estado **q**0 y lee un **1** en la cinta, cambiará al estado **q**1, escribirá un **0** en la cinta y moverá el cabezal a la derecha. 29 | 30 | 5. **Estado inicial (𝑞0**) : 31 | 32 | * **q**0 es el estado en el que se encuentra la MT al inicio de la computación. 33 | * Ejemplo: 𝑞0=**q**0. 34 | 35 | 6. **Estado de aceptación (𝑞acept**q**acept****)** : 36 | 37 | * **q**acept es uno de los estados de **Q** donde la MT entra si acepta la cadena de entrada. 38 | * La MT se detiene si entra en 𝑞acept. 39 | * Ejemplo: 𝑞acept=𝑞acept. 40 | 41 | 7. **Estado de rechazo (𝑞rech)** : 42 | 43 | * 𝑞rech es uno de los estados de 𝑄 donde la MT entra si rechaza la cadena de entrada. 44 | * La MT se detiene si entra en 𝑞rech. 45 | * Ejemplo: 𝑞rech=𝑞rech. 46 | 47 | ### Funcionamiento de una Máquina de Turing 48 | 49 | El funcionamiento de una MT puede describirse como sigue: 50 | 51 | 1. **Inicio** : 52 | 53 | * La MT comienza en el estado inicial 𝑞0. 54 | * El cabezal de la MT se coloca en el primer símbolo de la cinta. 55 | 56 | 2. **Lectura y escritura** : 57 | 58 | * En cada paso, la MT lee el símbolo en la celda actual de la cinta. 59 | * Con base en el estado actual y el símbolo leído, la MT usa la función de transición 𝛿 para determinar el siguiente estado, el símbolo que debe escribir en la cinta, y la dirección en la que debe mover el cabezal. 60 | 61 | 3. **Movimiento del cabezal** : 62 | 63 | * El cabezal se mueve a la izquierda o a la derecha dependiendo de la dirección especificada por **δ**. 64 | 65 | 1. **Detención** : 66 | 67 | * La MT se detiene cuando entra en uno de los estados de aceptación 𝑞acept o de rechazo 𝑞rech. 68 | 69 | ### Ejemplo Ilustrativo 70 | 71 | Supongamos que queremos construir una MT que acepte cadenas que consisten en un número impar de unos (1s). La MT puede ser definida como: 72 | 73 | * 𝑄={𝑞0,𝑞1,𝑞acept,𝑞rech} 74 | * Σ={1} 75 | * Γ={1,⊔} 76 | * 𝛿 definida como: 77 | * 𝛿(𝑞0,1)=(𝑞1,1,𝑅) 78 | * 𝛿(𝑞1,1)=(𝑞0,1,𝑅) 79 | * 𝛿(𝑞0,⊔)=(𝑞rech,⊔,𝑅) 80 | * 𝛿(𝑞1,⊔)=(𝑞acept,⊔,𝑅) 81 | * 𝑞0 es el estado inicial. 82 | * 𝑞acept=𝑞acept. 83 | * 𝑞rech=𝑞rech. 84 | 85 | ### Descripción del Ejemplo 86 | 87 | * Comienza en 𝑞0. 88 | * Lee un 1, cambia a 𝑞1, sigue leyendo y cambiando entre 𝑞0 y 𝑞1 por cada 1. 89 | * Si la cinta termina en 𝑞1 (número impar de 1s), acepta. 90 | * Si la cinta termina en 𝑞0 (número par de 1s), rechaza. 91 | 92 | Este ejemplo ilustra cómo la MT procesa símbolos en la cinta y transiciones entre estados para decidir si acepta o rechaza una cadena. 93 | -------------------------------------------------------------------------------- /Tema6/tema6/6_2.md: -------------------------------------------------------------------------------- 1 | # 6.2 Construcción Modular de una Máquina de Turing 2 | 3 | La construcción modular implica diseñar una Máquina de Turing compleja a partir de componentes más simples. Cada módulo o submáquina resuelve una sub-tarea específica, y luego estos módulos se integran para resolver el problema completo. Aquí se presentan los pasos y conceptos clave para la construcción modular: 4 | 5 | ## 1. Identificar Sub-problemas 6 | 7 | Dividir el problema principal en varios sub-problemas más pequeños y manejables. Cada sub-problema se resolverá por un módulo específico de la MT. 8 | 9 | **Ejemplo:** Si queremos diseñar una MT para sumar dos números binarios, podemos dividir el problema en módulos como: 10 | 11 | * Leer el primer número binario. 12 | * Leer el segundo número binario. 13 | * Realizar la suma binaria. 14 | * Escribir el resultado. 15 | 16 | ## 2. Diseñar Módulos 17 | 18 | Para cada sub-problema, se diseña una MT parcial o un sub-módulo que lo resuelva. Cada módulo tiene su propia definición de estados, alfabeto y funciones de transición. 19 | 20 | **Ejemplo:** 21 | 22 | * Módulo 1: Leer el primer número binario. 23 | * Módulo 2: Leer el segundo número binario. 24 | * Módulo 3: Realizar la suma binaria. 25 | * Módulo 4: Escribir el resultado. 26 | 27 | Cada módulo tendrá una estructura similar a la de una MT, definida por sus propios estados y transiciones. 28 | 29 | ## 3. Combinar Módulos 30 | 31 | Conectar los módulos entre sí. Esto se hace diseñando estados y transiciones adicionales que permitan que el resultado de un módulo sea la entrada para el siguiente. La combinación puede requerir la definición de nuevos estados de inicio y final para cada módulo. 32 | 33 | **Ejemplo:** 34 | 35 | * Estados iniciales y finales de cada módulo se ajustan para que la MT pueda pasar de un módulo a otro de manera secuencial. 36 | * Se crean transiciones que permitan el cambio de un módulo al siguiente una vez que se completa la tarea del módulo actual. 37 | 38 | ## 4. Estados Intermedios y Comunicación 39 | 40 | Asegurarse de que los estados de aceptación y rechazo de cada módulo no interfieran con los otros módulos a menos que esa sea la intención. Usar estados intermedios para la comunicación entre módulos. 41 | 42 | **Ejemplo:** 43 | 44 | * Si el Módulo 1 termina de leer el primer número, debe pasar al Módulo 2 sin entrar en un estado de aceptación o rechazo. 45 | * Se puede usar un estado intermedio 𝑞trans**q**trans para pasar de Módulo 1 a Módulo 2. 46 | 47 | ### Ejemplo Detallado 48 | 49 | Supongamos que queremos diseñar una MT para decidir si una cadena binaria contiene el mismo número de 0s y 1s. Dividimos el problema en módulos: 50 | 51 | 1. **Módulo 1:** Contar los 0s. 52 | 2. **Módulo 2:** Contar los 1s. 53 | 3. **Módulo 3:** Comparar las cantidades y aceptar o rechazar. 54 | 55 | #### Módulo 1: Contar los 0s 56 | 57 | * **Estados:** 𝑄1={𝑞1,0,𝑞1,1,𝑞1,trans} 58 | * **Función de Transición:** 59 | * 𝛿1(𝑞1,0,0)=(𝑞1,0,0,𝑅) 60 | * 𝛿1(𝑞1,0,1)=(𝑞1,0,1,𝑅) 61 | * 𝛿1(𝑞1,0,⊔)=(𝑞1,trans,⊔,𝐿) 62 | 63 | #### Módulo 2: Contar los 1s 64 | 65 | * **Estados:** 𝑄2={𝑞2,0,𝑞2,1,𝑞2,trans} 66 | * **Función de Transición:** 67 | * 𝛿2(𝑞2,0,1)=(𝑞2,0,1,𝑅) 68 | * 𝛿2(𝑞2,0,0)=(𝑞2,0,0,𝑅) 69 | * 𝛿2(𝑞2,0,⊔)=(𝑞2,trans,⊔,𝐿) 70 | 71 | #### Módulo 3: Comparar cantidades 72 | 73 | * **Estados:** 𝑄3={𝑞3,0,𝑞acept,𝑞rech} 74 | * **Función de Transición:** 75 | * 𝛿3(𝑞3,0,igual)=(𝑞acept,⊔,𝑅) 76 | * 𝛿3(𝑞3,0,diferente)=(𝑞rech,⊔,𝑅) 77 | 78 | ### Integración de Módulos 79 | 80 | #### Conjunto de Estados Combinado 81 | 82 | * 𝑄=𝑄1∪𝑄2∪𝑄3 83 | * **δ** se ajusta para manejar las transiciones entre módulos. 84 | 85 | #### Estados Iniciales y Finales 86 | 87 | * Estado inicial 𝑞0 del Módulo 1. 88 | * Estado de transición 𝑞1,trans a 𝑞2,0 del Módulo 2. 89 | * Estado de transición 𝑞2,trans a 𝑞3,0 del Módulo 3. 90 | * Estados de aceptación y rechazo definidos en 𝑞acept y 𝑞rech. 91 | 92 | ### Transiciones entre Módulos 93 | 94 | * 𝛿(𝑞1,trans,⊔)=(𝑞2,0,⊔,𝑅) 95 | * 𝛿(𝑞2,trans,⊔)=(𝑞3,0,⊔,𝑅) 96 | 97 | ## Resumen de la Construcción Modular 98 | 99 | 1. **Identificación de sub-problemas.** 100 | 2. **Diseño de módulos específicos para cada sub-problema.** 101 | 3. **Definición de estados y transiciones que conecten los módulos.** 102 | 4. **Integración y ajustes para asegurar la correcta comunicación y secuencia entre módulos.** 103 | -------------------------------------------------------------------------------- /Tema6/tema6/6_3.md: -------------------------------------------------------------------------------- 1 | # 6.3 Lenguajes Aceptados por las Máquinas de Turing 2 | 3 | Los lenguajes aceptados por una Máquina de Turing son conocidos como **lenguajes recursivamente enumerables (RE)** . Un lenguaje 𝐿⊆Σ es recursivamente enumerable si existe una MT que acepta exactamente las cadenas en **L**. 4 | 5 | Se dividen en dos categorías principales: lenguajes recursivamente enumerables y lenguajes recursivos. Veamos estos conceptos en detalle: 6 | 7 | ## 1. Lenguajes Recursivamente Enumerables (RE) 8 | 9 | Un lenguaje **L**⊆**Σ** es recursivamente enumerable si existe una Máquina de Turing que acepta exactamente las cadenas en **L**. Es decir, una MT que: 10 | 11 | * Acepta (llega a un estado de aceptación) todas las cadenas que pertenecen a **L**. 12 | * Puede no detenerse (entrar en un bucle infinito) para las cadenas que no pertenecen a **L**. 13 | 14 | **Definición Formal** : 15 | Un lenguaje **L** es recursivamente enumerable si existe una Máquina de Turing **M** tal que para cualquier cadena **𝑤∈Σ**: 16 | 17 | * Si **𝑤∈𝐿**, entonces 𝑀 eventualmente entra en un estado de aceptación. 18 | * Si **𝑤∉𝐿**, 𝑀 puede entrar en un estado de rechazo o puede nunca detenerse. 19 | 20 | **Ejemplo** : 21 | Consideremos el lenguaje 𝐿={𝑤∈{𝑎,𝑏}∗∣**w** contiene un número igual de 𝑎s y 𝑏s }. Una MT puede ser diseñada para contar el número de 𝑎s y 𝑏s y aceptar si son iguales. 22 | 23 | ## 2. Lenguajes Recursivos (Decidibles) 24 | 25 | Un lenguaje 𝐿 es recursivo si existe una Máquina de Turing que decide 𝐿. Esto significa que la MT: 26 | 27 | * Siempre se detiene (llega a un estado de aceptación o rechazo) para cualquier cadena de entrada. 28 | * Acepta las cadenas que pertenecen a 𝐿 y rechaza las que no pertenecen a 𝐿. 29 | 30 | **Definición Formal** : 31 | Un lenguaje 𝐿 es recursivo si existe una Máquina de Turing 𝑀 tal que para cualquier cadena 𝑤∈Σ∗: 32 | 33 | * Si 𝑤∈𝐿, entonces 𝑀 eventualmente entra en un estado de aceptación. 34 | * Si 𝑤∉𝐿, entonces 𝑀 eventualmente entra en un estado de rechazo. 35 | 36 | **Ejemplo** : 37 | Consideremos el lenguaje 𝐿={𝑤∈{0,1}∗∣**w** es un palíndromo }. Una MT puede ser diseñada para comparar los caracteres de los extremos de la cadena hacia el centro y aceptar si todos los caracteres coinciden. 38 | 39 | ## Diferencias Clave entre Lenguajes RE y Recursivos 40 | 41 | 1. **Detenibilidad** : 42 | 43 | * **Lenguajes Recursivos** : La MT siempre se detiene y decide si una cadena pertenece o no al lenguaje. 44 | * **Lenguajes Recursivamente Enumerables** : La MT puede no detenerse para cadenas que no pertenecen al lenguaje. 45 | 46 | 1. **Aceptación y Rechazo** : 47 | 48 | * **Lenguajes Recursivos** : La MT tiene estados explícitos de aceptación y rechazo para todas las entradas. 49 | * **Lenguajes Recursivamente Enumerables** : La MT tiene un estado de aceptación para las cadenas en el lenguaje, pero puede no tener un estado explícito de rechazo (puede no detenerse para cadenas fuera del lenguaje). 50 | 51 | ### Ejemplos de Lenguajes RE y Recursivos 52 | 53 | * **Lenguaje Recursivo** : 54 | 𝐿={0𝑛1𝑛∣𝑛≥0}: Este lenguaje contiene cadenas con 𝑛 ceros seguidos de 𝑛 unos. Una MT puede ser diseñada para comprobar que el número de ceros y unos son iguales y, por lo tanto, se detendrá siempre con una decisión. 55 | * **Lenguaje Recursivamente Enumerable** : 56 | 𝐿={𝑤∈{0,1}∗∣MT 𝑀 acepta 𝑤}: Este es el lenguaje de la descripción de todas las cadenas aceptadas por una MT específica 𝑀. Aquí, 𝑀 puede no detenerse para algunas cadenas que no pertenecen al lenguaje, haciendo que el lenguaje sea recursivamente enumerable pero no necesariamente recursivo. 57 | 58 | ### Relación con el Problema de la Parada 59 | 60 | El Problema de la Parada (Halting Problem) es un ejemplo clásico que demuestra la diferencia entre lenguajes recursivos y recursivamente enumerables. El problema pregunta si una MT 𝑀 se detendrá en una entrada 𝑤. 61 | 62 | * **Lenguaje** : 𝐿={⟨𝑀,𝑤⟩∣𝑀 se detiene en 𝑤} 63 | * Este lenguaje es recursivamente enumerable porque podemos diseñar una MT que simule 𝑀 en 𝑤 y acepte si 𝑀 se detiene. Sin embargo, si 𝑀 no se detiene, la simulación continúa indefinidamente. 64 | * No es recursivo porque no existe una MT que decida en tiempo finito si 𝑀 se detendrá para cualquier entrada 𝑤. 65 | 66 | ### Resumen 67 | 68 | * **Lenguajes Recursivamente Enumerables (RE)** : Aceptados por una MT, pero la MT puede no detenerse para cadenas fuera del lenguaje. 69 | * **Lenguajes Recursivos** : Decididos por una MT que siempre se detiene y da una respuesta (acepta o rechaza). 70 | 71 | Estos conceptos son fundamentales en la teoría de la computación y nos ayudan a comprender los límites de lo que es computable por las máquinas. 72 | -------------------------------------------------------------------------------- /Tema5/tema5/5_5.md: -------------------------------------------------------------------------------- 1 | # 5.5 Diagramas de Sintaxis 2 | 3 | Los diagramas de sintaxis, también conocidos como diagramas de árbol de sintaxis o diagramas de análisis sintáctico, son representaciones gráficas de la estructura sintáctica de una expresión o cadena en un lenguaje formal. Estos diagramas proporcionan una representación visual intuitiva de cómo se construye la expresión a partir de las reglas de una gramática dada. 4 | 5 | ## Componentes de los diagramas de sintaxis: 6 | 7 | 1. **Nodos** : Cada nodo del diagrama representa un símbolo en la cadena o expresión. Los nodos pueden ser terminales (representando símbolos del lenguaje) o no terminales (representando reglas de producción o categorías gramaticales). 8 | 2. **Conexiones** : Las conexiones entre los nodos representan las relaciones entre ellos, indicando cómo se combinan los símbolos para formar la expresión. Por lo general, las conexiones se dibujan como flechas que van desde los nodos no terminales a los nodos terminales o a otros nodos no terminales, siguiendo las reglas de producción de la gramática. 9 | 10 | ## Ejemplo de un diagrama de sintaxis: 11 | 12 | Consideremos la siguiente gramática simple: 13 | 14 |
css
S -> A + B 15 | A -> a 16 | B -> b 17 |
18 | 19 | Para la expresión "a + b", el diagrama de sintaxis sería: 20 | 21 |
css
S 22 | / \ 23 | A + 24 | | | 25 | a B 26 | | 27 | b 28 |
29 | 30 | En este diagrama, el nodo superior "S" representa la regla de producción inicial "S -> A + B". El nodo "A" representa la regla de producción "A -> a", y el nodo "B" representa la regla de producción "B -> b". La conexión entre "S" y "A" indica que la regla "S -> A + B" se aplica, y la conexión entre "S" y "+" indica que se ha encontrado el símbolo terminal "+". Similarmente, la conexión entre "+" y "B" indica que se ha encontrado el símbolo terminal "b". 31 | 32 | ## Uso de diagramas de sintaxis: 33 | 34 | Los diagramas de sintaxis son útiles en varios contextos, incluyendo: 35 | 36 | * **Análisis sintáctico** : Los diagramas de sintaxis se utilizan en los analizadores sintácticos para representar la estructura sintáctica de las expresiones y facilitar la verificación de la corrección sintáctica. 37 | * **Comprensión del lenguaje** : En el procesamiento de lenguaje natural y la lingüística computacional, los diagramas de sintaxis se utilizan para comprender la estructura sintáctica de las oraciones en un idioma natural. 38 | * **Depuración** : Los diagramas de sintaxis son útiles para depurar errores en el análisis sintáctico. Al visualizar la estructura de una expresión, es más fácil identificar dónde ocurren los errores. 39 | 40 | En resumen, los diagramas de sintaxis son una herramienta visual poderosa para representar y comprender la estructura sintáctica de expresiones en lenguajes formales. Proporcionan una representación intuitiva y fácil de entender de cómo se combinan los símbolos de acuerdo con las reglas de una gramática dada. 41 | -------------------------------------------------------------------------------- /Tema5/tema5/5_3.md: -------------------------------------------------------------------------------- 1 | # 5.3 Árboles de derivación 2 | 3 | Los árboles de derivación son estructuras arborescentes que representan cómo se derivan o generan las cadenas de un lenguaje a partir de una gramática dada. Cada nodo del árbol representa un símbolo en la cadena generada, y las aristas representan las reglas de producción aplicadas para generar la cadena. Los árboles de derivación son una forma visual de entender cómo se aplican las reglas de producción de una gramática para generar una cadena específica. 4 | 5 | ## Componentes de un árbol de derivación: 6 | 7 | 1. **Nodos** : Cada nodo del árbol representa un símbolo en la cadena generada por la gramática. Los nodos pueden ser símbolos terminales (hojas) o símbolos no terminales. 8 | 2. **Raíz** : El nodo superior del árbol se llama raíz y representa el símbolo inicial de la gramática, a partir del cual se inicia la generación de la cadena. 9 | 3. **Hojas** : Los nodos del árbol que no tienen hijos se llaman hojas y representan símbolos terminales en la cadena generada. 10 | 4. **Reglas de producción** : Cada arista del árbol representa una regla de producción aplicada para derivar un símbolo no terminal en sus hijos. La etiqueta de la arista indica qué regla de producción se utilizó. 11 | 12 | ## Ejemplo de árbol de derivación: 13 | 14 | Consideremos la siguiente gramática libre de contexto: 15 | 16 |
css
S -> AB 17 | A -> a 18 | B -> b 19 |
20 | 21 | Ahora, vamos a generar la cadena "ab" utilizando esta gramática. El árbol de derivación para esta cadena sería: 22 | 23 |
css
S 24 | / \ 25 | A B 26 | / \ 27 | a b 28 |
29 | 30 | En este árbol de derivación, el nodo raíz "S" se deriva en los nodos "A" y "B" mediante la regla de producción "S -> AB". Luego, el nodo "A" se deriva en el símbolo terminal "a" mediante la regla de producción "A -> a", y el nodo "B" se deriva en el símbolo terminal "b" mediante la regla de producción "B -> b". 31 | 32 | ## Uso de árboles de derivación: 33 | 34 | Los árboles de derivación son útiles en varios contextos, incluyendo: 35 | 36 | * **Análisis sintáctico** : En la fase de análisis de un compilador, los árboles de derivación se utilizan para representar la estructura sintáctica de las cadenas de entrada y facilitar la verificación de la corrección sintáctica. 37 | * **Depuración** : Los árboles de derivación son útiles para depurar errores en el análisis sintáctico. Al visualizar cómo se deriva una cadena, es más fácil identificar dónde ocurren los errores. 38 | * **Comprensión del lenguaje** : En el procesamiento de lenguaje natural y en la lingüística computacional, los árboles de derivación se utilizan para comprender la estructura sintáctica de las oraciones en un idioma natural. 39 | 40 | En resumen, los árboles de derivación son una herramienta fundamental en el estudio y análisis de las gramáticas libres de contexto. Proporcionan una representación visual intuitiva de cómo se generan las cadenas de un lenguaje a partir de una gramática dada. 41 | -------------------------------------------------------------------------------- /Tema5/tema5/5_4.md: -------------------------------------------------------------------------------- 1 | # 5.4 Formas normales de Chomsky 2 | 3 | Las formas normales de Chomsky son representaciones estándar de las gramáticas libres de contexto que simplifican su estructura y facilitan su análisis y manipulación. Estas formas normales fueron propuestas por el matemático Noam Chomsky y son ampliamente utilizadas en la teoría de la computación y la lingüística computacional. 4 | 5 | ## Forma Normal de Chomsky (FNC): 6 | 7 | En la Forma Normal de Chomsky, todas las reglas de producción tienen una de las siguientes formas: 8 | 9 | 1. A -> BC, donde A, B y C son símbolos no terminales. 10 | 2. A -> a, donde A es un símbolo no terminal y a es un símbolo terminal. 11 | 3. S -> ε, donde S es el símbolo inicial y ε representa la cadena vacía (epsilon). 12 | 13 | ## Pasos para convertir una gramática a FNC: 14 | 15 | 1. Eliminar reglas que generen cadenas vacías (ε-producciones), a menos que la cadena vacía sea la única cadena generada por el símbolo inicial. 16 | 2. Eliminar reglas unitarias, es decir, reglas de la forma A -> B, donde A y B son símbolos no terminales. 17 | 3. Eliminar reglas de producción con más de dos símbolos no terminales en el lado derecho. Para esto, se pueden introducir nuevos símbolos no terminales intermedios y descomponer las reglas existentes. 18 | 4. Reemplazar símbolos terminales por símbolos no terminales, introduciendo nuevas reglas de producción para cada terminal. 19 | 5. Asegurarse de que el símbolo inicial aparezca solo en una regla de producción, y que esa regla sea S -> α, donde α es una cadena de símbolos no terminales. 20 | 21 | ## Ejemplo de conversión a FNC: 22 | 23 | Considere la siguiente gramática: 24 | 25 |
css
S -> AB | a 26 | A -> BC | b | ε 27 | B -> a 28 | C -> AB | b 29 |
30 | 31 | Pasos para convertirla a FNC: 32 | 33 | 1. Eliminar reglas que generen cadenas vacías: A -> ε. 34 | 2. Eliminar reglas unitarias: ninguna. 35 | 3. Reemplazar reglas con más de dos símbolos no terminales: 36 | * A -> BC se convierte en: 37 | * A -> D 38 | * D -> BC 39 | * C -> AB se convierte en: 40 | * C -> E 41 | * E -> AB 42 | 4. Reemplazar símbolos terminales por símbolos no terminales: ninguna necesaria. 43 | 5. Asegurarse de que el símbolo inicial tenga solo una regla de producción: ninguna necesaria. 44 | 45 | La gramática convertida a FNC sería: 46 | 47 |
css
S -> DE | a 48 | A -> D | b 49 | B -> a 50 | C -> E | b 51 | D -> BC 52 | E -> AB 53 |
54 | 55 | ## Ventajas de la FNC: 56 | 57 | * **Facilita el análisis** : Las gramáticas en FNC son más fáciles de analizar y manipular debido a su estructura regular y bien definida. 58 | * **Simplifica los algoritmos** : Muchos algoritmos y técnicas en teoría de la computación están diseñados específicamente para trabajar con gramáticas en FNC. 59 | * **Elimina ambigüedades** : La FNC elimina la ambigüedad en la representación de las producciones de una gramática, lo que facilita la interpretación unívoca de las cadenas generadas. 60 | 61 | En resumen, las formas normales de Chomsky proporcionan una representación estándar y simplificada de las gramáticas libres de contexto, lo que facilita su estudio, análisis y manipulación en una variedad de contextos computacionales y lingüísticos. 62 | -------------------------------------------------------------------------------- /Tema5/tema5/5_6.md: -------------------------------------------------------------------------------- 1 | # 5.6 Eliminación de la ambigüedad 2 | 3 | La ambigüedad en una gramática ocurre cuando una cadena en el lenguaje puede ser generada por más de una secuencia de derivaciones. Esto puede dificultar la interpretación precisa de la estructura sintáctica de una expresión y puede llevar a resultados no deseados en el análisis sintáctico. La eliminación de la ambigüedad es un paso importante en el diseño y la implementación de gramáticas para garantizar que las expresiones sean interpretadas de manera única. 4 | 5 | ## Técnicas para eliminar la ambigüedad: 6 | 7 | 1. **Reescribir las reglas de producción** : Una técnica común para eliminar la ambigüedad es reescribir las reglas de producción de la gramática de manera que la ambigüedad se elimine. Esto puede implicar descomponer reglas ambiguas en múltiples reglas más específicas o cambiar el orden de aplicación de las reglas para garantizar que la derivación sea única. 8 | 2. **Asociatividad y precedencia** : En el caso de gramáticas que involucran operadores aritméticos u otras construcciones con asociatividad y precedencia, se pueden definir reglas explícitas para manejar estos casos de manera unívoca. Esto puede incluir la definición de reglas de precedencia y asociatividad para los operadores, así como la introducción de reglas adicionales para resolver ambigüedades. 9 | 3. **Factoring y left-factoring** : Estas técnicas implican la reorganización de las reglas de producción para evitar la ambigüedad. Factoring implica la identificación y extracción de partes comunes de reglas de producción para reducir la ambigüedad, mientras que left-factoring implica la modificación de las reglas para que todas comiencen con el mismo prefijo, lo que ayuda a resolver conflictos de elección en el análisis sintáctico. 10 | 4. **Gramáticas semánticas** : En algunos casos, la ambigüedad puede resolverse mediante la introducción de información semántica en la gramática. Esto puede implicar la definición de restricciones semánticas adicionales que limiten las interpretaciones válidas de una expresión, lo que ayuda a eliminar ambigüedades al restringir las posibles derivaciones. 11 | 12 | ## Ejemplo de eliminación de ambigüedad: 13 | 14 | Consideremos la siguiente gramática ambigua para expresiones aritméticas: 15 | 16 |
mathematica
E -> E + E | E * E | (E) | num 17 |
18 | 19 | Esta gramática es ambigua porque no especifica claramente el orden de evaluación de los operadores aritméticos. Para eliminar la ambigüedad, podríamos introducir reglas de precedencia y asociatividad: 20 | 21 |
r
E -> E + T | T 22 | T -> T * F | F 23 | F -> (E) | num 24 |
25 | 26 | En esta gramática, hemos introducido la regla de precedencia de la suma sobre la multiplicación. Ahora, las expresiones serán interpretadas de manera única, resolviendo así la ambigüedad. 27 | 28 | ## Importancia de la eliminación de la ambigüedad: 29 | 30 | * **Interpretación única** : La eliminación de la ambigüedad garantiza que cada cadena en el lenguaje tenga una única interpretación sintáctica, lo que facilita su análisis y comprensión. 31 | * **Compatibilidad con algoritmos de análisis sintáctico** : Muchos algoritmos de análisis sintáctico están diseñados para trabajar con gramáticas no ambiguas. La eliminación de la ambigüedad permite aplicar estos algoritmos de manera efectiva. 32 | * **Evita resultados inesperados** : La ambigüedad puede llevar a resultados inesperados o ambiguos en el análisis sintáctico. Al eliminar la ambigüedad, se garantiza que el análisis produzca resultados precisos y consistentes. 33 | 34 | En resumen, la eliminación de la ambigüedad es un paso importante en el diseño y la implementación de gramáticas, ya que garantiza una interpretación única y unívoca de las expresiones en el lenguaje, facilitando así su análisis y procesamiento. 35 | -------------------------------------------------------------------------------- /Tema1/Tema1/1_1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# **1.1 Alfabeto**\n", 8 | "\n", 9 | "**Definición y ejemplos de alfabetos:**\n", 10 | "\n", 11 | "En teoría de lenguajes formales, un alfabeto es un conjunto finito de símbolos que se utilizan para construir cadenas. Los símbolos pueden ser letras, números, signos de puntuación, caracteres especiales, entre otros. Un alfabeto se denota por una letra mayúscula, como Σ.\n", 12 | "\n", 13 | "Por ejemplo, el alfabeto Σ = {0, 1} se utiliza para construir cadenas binarias, donde cada símbolo representa un bit. Otro ejemplo es el alfabeto Σ = {a, b, c} que se utiliza para construir cadenas de caracteres.\n", 14 | "\n", 15 | "**Concepto de símbolos y caracteres:**\n", 16 | "\n", 17 | "Un símbolo es un elemento básico de un alfabeto. Puede ser cualquier cosa, desde una letra hasta un número o un signo de puntuación. Un carácter es una representación visual de un símbolo. Por ejemplo, la letra \"a\" es un carácter que representa el símbolo \"a\".\n", 18 | "\n", 19 | "Es importante tener en cuenta que un carácter puede tener diferentes representaciones en diferentes sistemas de codificación de caracteres. Por ejemplo, en el sistema ASCII, la letra \"a\" se representa por el número 97, mientras que en el sistema Unicode, se representa por el número 97 también, pero con una codificación diferente.\n", 20 | "\n", 21 | "[Video Alfabetos, Cadenas y Lenguajes](https://youtu.be/0x_dbHi7dEY?si=igBi8C1Qr4fDpg2R)\n", 22 | "\n", 23 | "**Cómo representar alfabetos en Python:**\n", 24 | "\n", 25 | "En Python, un alfabeto se puede representar como una lista o un conjunto de símbolos. Por ejemplo, el alfabeto Σ = {0, 1} se puede representar como una lista en Python de la siguiente manera:\n", 26 | "\n", 27 | "``sigma = ['0', '1'] ``\n", 28 | "\n", 29 | "También se puede representar como un conjunto en Python de la siguiente manera:\n", 30 | "\n", 31 | "``sigma = {'0', '1'}``\n", 32 | "\n", 33 | "La elección de la representación depende del contexto y de las operaciones que se quieran realizar con el alfabeto. Por ejemplo, si se quiere verificar si un símbolo pertenece al alfabeto, es más eficiente utilizar un conjunto en lugar de una lista.\n", 34 | "\n", 35 | "Es importante tener en cuenta que en Python, los símbolos en un alfabeto se representan como cadenas de caracteres. Por lo tanto, es importante utilizar comillas simples o dobles para representar los símbolos en la lista o conjunto.\n", 36 | "\n", 37 | "En la teoría de lenguajes formales, el alfabeto es crucial ya que representa el conjunto de símbolos sobre los cuales se construyen las cadenas en un lenguaje formal. Esto es fundamental para comprender la estructura y funcionamiento de los lenguajes formales.\n", 38 | "\n", 39 | "En Python, los alfabetos pueden ser representados mediante listas o conjuntos. A continuación, te proporcionaré ejemplos con ambos enfoques:\n", 40 | "\n", 41 | "Representación de Alfabetos en Python utilizando Listas:" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 4, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "name": "stdout", 51 | "output_type": "stream", 52 | "text": [ 53 | "La cadena \"10101\" pertenece al alfabeto.\n" 54 | ] 55 | } 56 | ], 57 | "source": [ 58 | "# Definición de un alfabeto utilizando una lista\n", 59 | "alfabeto_lista = ['0', '1'] # Alfabeto binario\n", 60 | "\n", 61 | "# Función que valida si una cadena pertenece a un alfabeto específico\n", 62 | "def pertenece_a_alfabeto(cadena, alfabeto):\n", 63 | " for caracter in cadena:\n", 64 | " if caracter not in alfabeto:\n", 65 | " return False\n", 66 | " return True\n", 67 | "\n", 68 | "# Ejemplo de validación de pertenencia a un alfabeto\n", 69 | "cadena_ejemplo = '10101'\n", 70 | "if pertenece_a_alfabeto(cadena_ejemplo, alfabeto_lista):\n", 71 | " print(f'La cadena \"{cadena_ejemplo}\" pertenece al alfabeto.')\n", 72 | "else:\n", 73 | " print(f'La cadena \"{cadena_ejemplo}\" no pertenece al alfabeto.')" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "Representación de Alfabetos en Python utilizando Conjuntos:" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 6, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "La cadena \"abcde\" pertenece al alfabeto.\n" 93 | ] 94 | } 95 | ], 96 | "source": [ 97 | "# Definición de un alfabeto utilizando un conjunto\n", 98 | "alfabeto_conjunto = {'a', 'b', 'c', 'd', 'e'} # Alfabeto con letras\n", 99 | "\n", 100 | "# Función que valida si una cadena pertenece a un alfabeto específico\n", 101 | "def pertenece_a_alfabeto(cadena, alfabeto):\n", 102 | " return set(cadena).issubset(alfabeto)\n", 103 | "\n", 104 | "# Ejemplo de validación de pertenencia a un alfabeto\n", 105 | "cadena_ejemplo = 'abcde'\n", 106 | "if pertenece_a_alfabeto(cadena_ejemplo, alfabeto_conjunto):\n", 107 | " print(f'La cadena \"{cadena_ejemplo}\" pertenece al alfabeto.')\n", 108 | "else:\n", 109 | " print(f'La cadena \"{cadena_ejemplo}\" no pertenece al alfabeto.')" 110 | ] 111 | } 112 | ], 113 | "metadata": { 114 | "kernelspec": { 115 | "display_name": "Python 3", 116 | "language": "python", 117 | "name": "python3" 118 | }, 119 | "language_info": { 120 | "codemirror_mode": { 121 | "name": "ipython", 122 | "version": 3 123 | }, 124 | "file_extension": ".py", 125 | "mimetype": "text/x-python", 126 | "name": "python", 127 | "nbconvert_exporter": "python", 128 | "pygments_lexer": "ipython3", 129 | "version": "3.10.11" 130 | } 131 | }, 132 | "nbformat": 4, 133 | "nbformat_minor": 2 134 | } 135 | -------------------------------------------------------------------------------- /Tema1/Tema1/1_5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 5 Fases de un compilador\n", 8 | "\n", 9 | "Las fases de un compilador son etapas fundamentales en el proceso de traducción del código fuente a un formato ejecutable. Cada fase cumple un papel específico en la transformación del código para su adecuada interpretación por la máquina. Las principales fases de un compilador incluyen el análisis léxico, el análisis sintáctico, el análisis semántico, la generación de código intermedio, la optimización de código y la generación de código objeto.\n", 10 | "\n", 11 | "A continuación, se ofrece un ejemplo simple de cada fase de un compilador y cómo podrían implementarse en Python:\n", 12 | "\n", 13 | " **1. Análisis léxico:** Esta fase se encarga de la identificación y análisis de los tokens del lenguaje, como palabras clave, identificadores, literales, etc. \n", 14 | "\n", 15 | "Aquí tienes un ejemplo simple utilizando expresiones regulares en Python:" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 3, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "name": "stdout", 25 | "output_type": "stream", 26 | "text": [ 27 | "[('a', 'a'), ('5', '5'), ('+', '+'), ('b', 'b')]\n" 28 | ] 29 | } 30 | ], 31 | "source": [ 32 | "import re\n", 33 | "\n", 34 | "# Definición de patrones para tokens\n", 35 | "patron_identificador = r'[a-zA-Z_]\\w*'\n", 36 | "patron_numero = r'\\d+'\n", 37 | "patron_operador = r'\\+|\\-|\\*|/'\n", 38 | "\n", 39 | "# Función de análisis léxico\n", 40 | "def analisis_lexico(codigo_fuente):\n", 41 | " tokens = []\n", 42 | " for token in re.finditer(f'({patron_identificador})|({patron_numero})|({patron_operador})', codigo_fuente):\n", 43 | " tipo = next(filter(None, token.groups()))\n", 44 | " tokens.append((tipo, token.group()))\n", 45 | " return tokens\n", 46 | "\n", 47 | "# Ejemplo de uso del análisis léxico\n", 48 | "codigo_fuente = \"a = 5 + b\"\n", 49 | "tokens = analisis_lexico(codigo_fuente)\n", 50 | "print(tokens)\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "**2. Análisis sintáctico:** En esta fase, se verifica la estructura gramatical del código fuente. \n", 58 | "\n", 59 | "Aquí un ejemplo sencillo utilizando una gramática libre de contexto:" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 4, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "ename": "ModuleNotFoundError", 69 | "evalue": "No module named 'ply'", 70 | "output_type": "error", 71 | "traceback": [ 72 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 73 | "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", 74 | "Cell \u001b[1;32mIn[4], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# Definición de una gramática sencilla para expresiones aritméticas\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mply\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01myacc\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01myacc\u001b[39;00m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# Definición de la gramática\u001b[39;00m\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mp_expresion\u001b[39m(p):\n", 75 | "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'ply'" 76 | ] 77 | } 78 | ], 79 | "source": [ 80 | "# Definición de una gramática sencilla para expresiones aritméticas\n", 81 | "import ply.yacc as yacc\n", 82 | "\n", 83 | "# Definición de la gramática\n", 84 | "def p_expresion(p):\n", 85 | " '''expresion : ID ASIGNACION expresion\n", 86 | " | expresion OPERADOR expresion\n", 87 | " | NUMERO'''\n", 88 | " # Lógica para comprobar la estructura sintáctica\n", 89 | "\n", 90 | "# Creación del analizador sintáctico\n", 91 | "analizador_sintactico = yacc.yacc()\n" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "**3. Análisis semántico:** Esta fase se encarga de verificar la coherencia del significado del código. \n", 99 | "\n", 100 | "Aquí un ejemplo sencillo que verifica la coherencia de operaciones aritméticas:" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "# Función para análisis semántico de operaciones aritméticas\n", 110 | "def analisis_semantico_expresion(expresion):\n", 111 | " # Lógica para verificar la coherencia semántica de la expresión\n", 112 | " " 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "**4. Generación de código intermedio, optimización de código y generación de código objeto:** Estas fases son críticas para la generación eficiente del código ejecutable. Implementar ejemplos sencillos para estas fases sería bastante complejo y extenso para el contexto actual, pero en general implican la transformación del código a una representación intermedia, la optimización de esta representación y la generación del código máquina respectivamente." 120 | ] 121 | } 122 | ], 123 | "metadata": { 124 | "kernelspec": { 125 | "display_name": "Python 3", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.12.0" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 2 144 | } 145 | -------------------------------------------------------------------------------- /Tema5/tema5/5_8.md: -------------------------------------------------------------------------------- 1 | # 5.8 Generación de matriz predictiva (cálculo de FIRST y FOLLOW) 2 | 3 | La generación de una matriz predictiva es un paso fundamental en la construcción de analizadores sintácticos predictivos para gramáticas libres de contexto. Implica la creación de una tabla que relaciona cada símbolo no terminal de la gramática con las producciones que pueden derivar de él, utilizando conjuntos FIRST y FOLLOW para determinar las reglas de producción adecuadas para aplicar en cada paso del análisis sintáctico. 4 | 5 | ## Conjunto FIRST: 6 | 7 | El conjunto FIRST de un símbolo no terminal o una cadena de símbolos en una gramática es el conjunto de terminales que pueden comenzar una cadena derivada de ese símbolo. Para calcular el conjunto FIRST de un símbolo no terminal o una cadena: 8 | 9 | 1. Si el símbolo es un terminal, el conjunto FIRST contiene solo ese terminal. 10 | 2. Si el símbolo es un no terminal, el conjunto FIRST contiene los terminales que comienzan alguna cadena derivada de ese no terminal. 11 | 3. Si el símbolo puede derivar la cadena vacía (ε), entonces ε también se incluye en el conjunto FIRST. 12 | 13 | ## Conjunto FOLLOW: 14 | 15 | El conjunto FOLLOW de un símbolo no terminal en una gramática es el conjunto de terminales que pueden aparecer inmediatamente después de ese símbolo en alguna derivación válida de la gramática. Para calcular el conjunto FOLLOW de un símbolo no terminal: 16 | 17 | 1. El símbolo inicial tiene $ al principio. 18 | 2. Si A → αBβ es una producción, entonces todo lo que puede estar en FIRST(β) excepto ε se incluye en FOLLOW(B). 19 | 3. Si A → αB es una producción o A → αBβ es una producción donde FIRST(β) contiene ε, entonces todo lo que está en FOLLOW(A) se incluye en FOLLOW(B). 20 | 21 | ## Generación de la matriz predictiva: 22 | 23 | Una vez que se han calculado los conjuntos FIRST y FOLLOW para cada símbolo no terminal en la gramática, se puede generar la matriz predictiva. Esta matriz tiene símbolos no terminales en las filas y terminales (incluyendo $ para fin de cadena) en las columnas. Cada celda de la matriz contiene las reglas de producción correspondientes que se aplicarán cuando el símbolo no terminal en esa fila se encuentre en la pila del analizador sintáctico y el terminal en esa columna sea el siguiente en la entrada. 24 | 25 | ### Ejemplo de cálculo de conjuntos FIRST y FOLLOW: 26 | 27 | Consideremos la siguiente gramática: 28 | 29 |
css
S -> A | ε 30 | A -> aB | b 31 | B -> c | ε 32 |
33 | 34 | Calcularíamos los conjuntos FIRST y FOLLOW de la siguiente manera: 35 | 36 |
scss
FIRST(S) = {a, b, ε} 37 | FIRST(A) = {a, b} 38 | FIRST(B) = {c, ε} 39 | 40 | FOLLOW(S) = {$} 41 | FOLLOW(A) = {c} 42 | FOLLOW(B) = {c} 43 |
44 | 45 | ### Ejemplo de matriz predictiva: 46 | 47 | Usando los conjuntos FIRST y FOLLOW calculados anteriormente, podemos construir la matriz predictiva para la gramática: 48 | 49 |
css
| a | b | c | $ 50 | -------------------------------- 51 | S | A | A | | ε 52 | A | aB | b | | 53 | B | | | c | ε 54 |
55 | 56 | En esta matriz, cada celda representa una regla de producción a aplicar. Por ejemplo, la celda en la fila S y la columna a contiene la regla de producción S -> A, porque FIRST(S) contiene a. 57 | 58 | ### Importancia de la matriz predictiva y los conjuntos FIRST y FOLLOW: 59 | 60 | * La matriz predictiva y los conjuntos FIRST y FOLLOW son esenciales para la construcción de analizadores sintácticos predictivos. 61 | * Permiten determinar las reglas de producción adecuadas para aplicar en cada paso del análisis sintáctico, lo que garantiza una interpretación unívoca de la estructura sintáctica de las cadenas del lenguaje. 62 | * Son fundamentales para el diseño y la implementación de compiladores y otros sistemas de procesamiento de lenguajes formales. 63 | 64 | En resumen, la generación de una matriz predictiva y el cálculo de conjuntos FIRST y FOLLOW son pasos cruciales en el análisis sintáctico de gramáticas libres de contexto. Proporcionan la base para la construcción de analizadores sintácticos predictivos eficientes y precisos. 65 | -------------------------------------------------------------------------------- /Tema2/Tema2/Practica.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Ejemplo de Expresiones Regulares\n", 8 | "\n", 9 | "Las expresiones regulares son herramientas poderosas para el procesamiento de texto y son ampliamente utilizadas en la programación para la búsqueda, validación y manipulación de cadenas de caracteres. Aquí te dejo una práctica básica que puedes realizar para interactuar con un bot utilizando expresiones regulares:\n", 10 | "\n", 11 | "Supongamos que estás construyendo un bot de asistencia para un sistema de reservas de vuelos. Tu tarea es escribir un script que pueda entender las consultas de los usuarios sobre vuelos utilizando expresiones regulares para identificar patrones comunes en las consultas. Por ejemplo, puedes reconocer patrones como \"Quiero volar de Ciudad de México a Japón el 25 de marzo\", \"¿Cuánto cuesta un vuelo de Madrid a París?\", \"Necesito un vuelo de ida y vuelta de Londres a Roma\", etc.\n", 12 | "\n", 13 | "Aquí hay un ejemplo de cómo podrías estructurar tu código en Python utilizando expresiones regulares:" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import re\n", 23 | "\n", 24 | "# Definir patrones de expresiones regulares\n", 25 | "patron_origen_destino_fecha = r\"volar de (\\w+) a (\\w+) el (\\d{1,2} de \\w+)\"\n", 26 | "patron_precio = r\"cuánto cuesta un vuelo de (\\w+) a (\\w+)\"\n", 27 | "patron_ida_vuelta = r\"un vuelo de ida y vuelta de (\\w+) a (\\w+)\"\n", 28 | "\n", 29 | "# Definir las consultas de los usuarios\n", 30 | "consultas = [\n", 31 | " \"Quiero volar de México a Japón el 25 de marzo\",\n", 32 | " \"¿Cuánto cuesta un vuelo de Madrid a París?\",\n", 33 | " \"Necesito un vuelo de ida y vuelta de Londres a Roma\"\n", 34 | "]\n", 35 | "\n", 36 | "# Procesar las consultas\n", 37 | "for consulta in consultas:\n", 38 | " # Verificar si la consulta coincide con alguno de los patrones\n", 39 | " if re.search(patron_origen_destino_fecha, consulta):\n", 40 | " origen_destino_fecha = re.search(patron_origen_destino_fecha, consulta)\n", 41 | " origen = origen_destino_fecha.group(1)\n", 42 | " destino = origen_destino_fecha.group(2)\n", 43 | " fecha = origen_destino_fecha.group(3)\n", 44 | " print(f\"Buscar vuelo de {origen} a {destino} para el {fecha}\")\n", 45 | " elif re.search(patron_precio, consulta):\n", 46 | " precio = re.search(patron_precio, consulta)\n", 47 | " origen = precio.group(1)\n", 48 | " destino = precio.group(2)\n", 49 | " print(f\"Consultar precio de vuelo de {origen} a {destino}\")\n", 50 | " elif re.search(patron_ida_vuelta, consulta):\n", 51 | " ida_vuelta = re.search(patron_ida_vuelta, consulta)\n", 52 | " origen = ida_vuelta.group(1)\n", 53 | " destino = ida_vuelta.group(2)\n", 54 | " print(f\"Buscar vuelo de ida y vuelta de {origen} a {destino}\")\n", 55 | " else:\n", 56 | " print(\"Lo siento, no puedo entender tu consulta.\")" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "# Crear un nuevo bot en Telegram\n", 64 | "\n", 65 | "## Paso 1\n", 66 | "\n", 67 | "1. Abre la aplicación de Telegram y busca el bot llamado *@BotFather*.\n", 68 | "2. Inicia una conversación con @BotFather y utiliza el comando /newbot para crear un nuevo bot.\n", 69 | "3. Sigue las instrucciones de @BotFather, proporciona un nombre para tu bot y luego un nombre de usuario único que termine en \"bot\" (por ejemplo, mi_primer_bot). Recibirás un mensaje con el token de acceso del bot.\n", 70 | "\n", 71 | "## Paso 2\n", 72 | "\n", 73 | "1. Asegúrate de tener Python instalado en tu sistema. Puedes descargarlo desde python.org.\n", 74 | "\n", 75 | "2. Instala la biblioteca python-telegram-bot que te ayudará a interactuar con la API de Telegram. Puedes instalarla utilizando pip:" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "pip install python-telegram-bot" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "## Paso 3: Escribir el código del bot\n", 92 | "\n", 93 | "Aquí hay un ejemplo de un bot simple que responde a mensajes utilizando expresiones regulares:\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "import logging\n", 103 | "import re\n", 104 | "\n", 105 | "from telegram import ForceReply, Update\n", 106 | "from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters\n", 107 | "\n", 108 | "# Enable logging\n", 109 | "logging.basicConfig(format=\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\", level=logging.INFO)\n", 110 | "logging.getLogger(\"httpx\").setLevel(logging.WARNING)\n", 111 | "logger = logging.getLogger(__name__)\n", 112 | "\n", 113 | "# Expresión regular para detectar mensajes que contienen \"Hola\"\n", 114 | "expresion_regular = re.compile(r\"hello|hi|hey|hola\", re.IGNORECASE)\n", 115 | "\n", 116 | "\n", 117 | "async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:\n", 118 | " \"\"\"Send a message when the command /start is issued.\"\"\"\n", 119 | " user = update.effective_user\n", 120 | " await update.message.reply_html(\n", 121 | " rf\"Hi {user.mention_html()}!\",\n", 122 | " reply_markup=ForceReply(selective=True),\n", 123 | " )\n", 124 | "\n", 125 | "\n", 126 | "async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:\n", 127 | " \"\"\"Send a message when the command /help is issued.\"\"\"\n", 128 | " await update.message.reply_text(\"Help!\")\n", 129 | "\n", 130 | "\n", 131 | "async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:\n", 132 | " \"\"\"Echo the user message if it matches the regular expression.\"\"\"\n", 133 | " message_text = update.message.text\n", 134 | " if expresion_regular.search(message_text):\n", 135 | " await update.message.reply_text(\"¡Hola! ¿Cómo estás?\")\n", 136 | " else:\n", 137 | " await update.message.reply_text(\"No entendí tu mensaje.\")\n", 138 | "\n", 139 | "\n", 140 | "def main() -> None:\n", 141 | " \"\"\"Start the bot.\"\"\"\n", 142 | " application = Application.builder().token(\"TOKEN\").build()\n", 143 | "\n", 144 | " application.add_handler(CommandHandler(\"start\", start))\n", 145 | " application.add_handler(CommandHandler(\"help\", help_command))\n", 146 | " application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))\n", 147 | "\n", 148 | " application.run_polling(allowed_updates=Update.ALL_TYPES)\n", 149 | "\n", 150 | "\n", 151 | "if __name__ == \"__main__\":\n", 152 | " main()\n" 153 | ] 154 | } 155 | ], 156 | "metadata": { 157 | "language_info": { 158 | "name": "python" 159 | } 160 | }, 161 | "nbformat": 4, 162 | "nbformat_minor": 2 163 | } 164 | -------------------------------------------------------------------------------- /Tema1/Tema1/1_2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 1.2 Cadenas\n", 8 | "\n", 9 | "Una cadena en el contexto de la teoría de lenguajes formales es una secuencia finita de símbolos tomados de un determinado alfabeto. Estas cadenas son elementos fundamentales en la construcción de los lenguajes formales, ya que conforman parte de los lenguajes definidos por el alfabeto.\n", 10 | "\n", 11 | "En Python, es posible generar todas las posibles combinaciones de cadenas a partir de un alfabeto dado utilizando recursión y manipulación de strings. A continuación, te proporcionaré un ejemplo de una función en Python que realiza esto:" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "Todas las posibles combinaciones de cadenas a partir del alfabeto dado:\n", 24 | "['0', '1', '00', '01', '10', '11', '000', '001', '010', '011', '100', '101', '110', '111']\n" 25 | ] 26 | } 27 | ], 28 | "source": [ 29 | "import itertools\n", 30 | "\n", 31 | "# Función que genera todas las posibles combinaciones de cadenas a partir de un alfabeto dado\n", 32 | "def combinaciones_cadenas(alfabeto, longitud_maxima):\n", 33 | " combinaciones = []\n", 34 | " for i in range(1, longitud_maxima + 1):\n", 35 | " # Utiliza itertools.product para generar todas las combinaciones de longitud i\n", 36 | " combinaciones.extend([''.join(x) for x in itertools.product(alfabeto, repeat=i)])\n", 37 | " return combinaciones\n", 38 | "\n", 39 | "# Definición del alfabeto\n", 40 | "alfabeto = ['0', '1'] # Alfabeto binario\n", 41 | "\n", 42 | "# Genera todas las combinaciones de cadenas a partir del alfabeto dado\n", 43 | "resultados = combinaciones_cadenas(alfabeto, 3) # Genera combinaciones de longitud hasta 3\n", 44 | "\n", 45 | "# Muestra el resultado\n", 46 | "print(\"Todas las posibles combinaciones de cadenas a partir del alfabeto dado:\")\n", 47 | "print(resultados)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "En este ejemplo, la función `combinaciones_cadenas` toma como argumentos un alfabeto y una longitud máxima, y utiliza la función `itertools.product` para generar todas las posibles combinaciones de cadenas con una longitud hasta la longitud máxima especificada. Las combinaciones resultantes se almacenan en una lista que se devuelve como resultado.\n", 55 | "\n", 56 | "Este es solo un ejemplo de cómo se puede implementar una función en Python para generar todas las posibles combinaciones de cadenas a partir de un alfabeto dado." 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "**Cadena Vacía**\n", 64 | "\n", 65 | "La cadena vacía se denota por ε y es la cadena que está formada por una secuencia vacía de símbolos de cualquier alfabeto.\n", 66 | "\n", 67 | "## **Operaciones con Cadenas**\n", 68 | "\n", 69 | "**Longitud de una Cadena**\n", 70 | "\n", 71 | "Si w es una cadena, decimos que la longitud de la misma es el número de símbolos que la forman y se denota por |w|. No importando cuantas veces aparezca el mismo símbolo en la cadena, cada ocurrencia se cuenta por separado.\n", 72 | "\n", 73 | "**Ejemplos**\n", 74 | "- Sea w1 = 10011, entonces |w1| = 5.\n", 75 | "- Sea w2 = 1011010101, entonces |w2| = 10.\n", 76 | "- La longitud de la cadena vacía es cero: |ε| = 0.\n", 77 | "\n", 78 | "**Concatenación**\n", 79 | "\n", 80 | "Concatenación es la yuxtaposición de dos cadenas, una a continuación de la otra, detal forma que si w y x son dos cadenas, la concatenación de w con x es la cadena que se obtiene de añadir la cadena x a la cadena w.\n", 81 | "\n", 82 | "La concatenación se denota con el operador de yuxtaposición: w $ x, pero usualmente se omite el punto, quedando: wx. Además, la yuxtaposición no es conmutativa y en general se tiene que: wx ≠ xw.\n", 83 | "\n", 84 | "La longitud de la concatenación es igual a la suma de las longitudes de las cadenas individuales: |wx| = |w| + |x| = |xw|.\n", 85 | "\n", 86 | "La concatenación de ε con cualquier cadena w no modifica a w. Es decir, la cadena vacía es el idéntico respecto a la concatenación, ya que: εw = wε = w. Por esto, a la cadena vacía se le conoce también como el Elemento Neutro de la Concatenación.\n", 87 | "\n", 88 | "\n", 89 | "**Potencia de Cadenas** \n", 90 | "Sea w una cadena formada a partir de un alfabeto Σ, entonces para cualquier n ≥ 0, se tiene que la enésima potencia de w se puede definir recursivamente como:\n", 91 | "\n", 92 | "\n", 93 | "\n", 94 | "Observe que en general se tiene que la concatenación de potencias es diferente de la potencia de la concatenación, esto es: (wx)n ≠ wnxn.\n", 95 | "\n", 96 | "**Prefijo**\n", 97 | "\n", 98 | "Sea w una cadena formada a partir de un alfabeto Σ, entonces, se cumple que existen dos cadenas x y z, tales que w = xz, se dice que x es un prefijo de w. Además, si z ≠ ε (es decir x ≠ w), se dice que x es un prefijo propio de w; en el otro caso, cuando x = w, se tiene que x es llamado prefijo impropio.\n", 99 | "Una cadena de longitud n, tiene n prefijos propios distintos y uno impropio.\n", 100 | "\n", 101 | "**Sufijo**\n", 102 | "\n", 103 | "Sea w una cadena formada a partir de un alfabeto Σ, entonces, se cumple que existen dos cadenas x y z, tales que w = xz, se dice que z es un sufijo de w. Además, si x ≠ ε (es decir z ≠ w), se puede decir que z es un sufijo propio de w; y similarmente al caso anterior, si z = w, entonces z es un sufijo impropio.\n", 104 | "\n", 105 | "Una cadena de longitud n, tiene n sufijos propios distintos y uno impropio.\n", 106 | "\n", 107 | "**Subcadenas**\n", 108 | "Una cadena y es una subcadena de otra cadena w, si existen x y z, no ambas vacías, para las cuales se cumple que w = xyz. Cualquier prefijo o sufijo propios de w también son subcadenas de w, en particular, ε es subcadena de cualquier otra cadena.\n", 109 | "\n", 110 | "La cantidad máxima N de subcadenas distintas de una cadena dada se puede determinar con la siguiente fórmula:\n", 111 | "\n", 112 | "\n", 113 | "\n", 114 | "Mientras que la cantidad mínima de subcadenas distintas de una cadena es n, para el caso en que la cadena esté formada por puros símbolos iguales.\n", 115 | "\n", 116 | "\n", 117 | "**Inversa de una Cadena**\n", 118 | "La Inversa de una Cadena w es la cadena wR, tal que es la imagen refleja de w, es decir, que equivale a w cuando se lee de derecha a izquierda.\n", 119 | "\n", 120 | "Formalmente se define la inversa de w de manera recursiva como:\n", 121 | "\n", 122 | "\n", 123 | "\n", 124 | "Donde y es una cadena y a es un símbolo.\n", 125 | "\n", 126 | "Propiedades de la Inversa\n", 127 | "\n", 128 | "- aR = a, donde a es un símbolo.\n", 129 | "- ( xR)R = x\n", 130 | "- Si x = wy, entonces xR = yRwR\n", 131 | "- Una cadena se llama palíndroma, cuando es igual a su inversa: w = wR.\n" 132 | ] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "Python 3", 138 | "language": "python", 139 | "name": "python3" 140 | }, 141 | "language_info": { 142 | "codemirror_mode": { 143 | "name": "ipython", 144 | "version": 3 145 | }, 146 | "file_extension": ".py", 147 | "mimetype": "text/x-python", 148 | "name": "python", 149 | "nbconvert_exporter": "python", 150 | "pygments_lexer": "ipython3", 151 | "version": "3.10.11" 152 | } 153 | }, 154 | "nbformat": 4, 155 | "nbformat_minor": 2 156 | } 157 | -------------------------------------------------------------------------------- /Tema3/Tema3/3_1.md: -------------------------------------------------------------------------------- 1 | # 3.1 Conceptos: Definición y Clasificación de Autómata Finito (AF) 2 | 3 | 1. **Introducción a los conceptos básicos de un autómata finito:** 4 | 5 | ## Definición: 6 | 7 | Un autómata finito es un modelo matemático abstracto que describe un sistema con un número finito de estados. Consiste en una serie de componentes, incluidos estados, un alfabeto de entrada, transiciones y un conjunto de estados de aceptación. Estos componentes interactúan para procesar una secuencia de símbolos de entrada y determinar si dicha secuencia es aceptada o rechazada por el autómata. 8 | 9 | ## Componentes de un autómata finito: 10 | 11 | 1. **Estados (Q):** Son las distintas situaciones o configuraciones en las que puede encontrarse el autómata en un momento dado. Se representan mediante círculos en los diagramas de estados. 12 | 2. **Alfabeto de entrada (Σ):** Es el conjunto finito de símbolos que el autómata puede leer como entrada. Estos símbolos pueden ser números, letras, u otros caracteres relevantes para el problema que se está modelando. 13 | 3. **Transiciones (δ):** Son las reglas que dictan cómo el autómata cambia de un estado a otro en respuesta a la entrada. Cada transición se define para un estado dado y un símbolo de entrada, y especifica el estado al que se moverá el autómata. 14 | 4. **Estado inicial (q₀):** Es el estado en el que el autómata se encuentra al inicio del proceso de procesamiento de la entrada. 15 | 5. **Estados de aceptación (F):** Son los estados en los que el autómata termina su proceso de procesamiento y acepta la entrada. No todos los estados necesariamente son de aceptación. 16 | 17 | ## Funcionamiento: 18 | 19 | 1. El autómata comienza en su estado inicial. 20 | 2. Lee los símbolos de entrada uno por uno. 21 | 3. En función del símbolo de entrada y el estado actual, el autómata sigue las transiciones definidas para moverse a otro estado. 22 | 4. Una vez que ha leído toda la entrada, el autómata verifica si ha llegado a un estado de aceptación. Si lo hace, acepta la entrada; de lo contrario, la rechaza. 23 | 24 | ## Aplicaciones: 25 | 26 | Los autómatas finitos se utilizan en una amplia gama de aplicaciones, incluidas: 27 | 28 | * Análisis léxico en compiladores. 29 | * Modelado de protocolos de comunicación. 30 | * Diseño de sistemas de control. 31 | * Análisis de cadenas de texto en procesamiento de lenguaje natural. 32 | * Implementación de juegos y juguetes electrónicos. 33 | 34 | En resumen, un autómata finito es un modelo matemático que describe sistemas con un número finito de estados y se utiliza para representar y analizar sistemas que pueden procesar secuencias de entrada siguiendo reglas específicas. Es una herramienta poderosa en la teoría de la computación y tiene numerosas aplicaciones en la práctica. 35 | 36 | * Ejemplos simples de aplicaciones de autómatas finitos en la vida cotidiana, como interruptores de luz, semáforos, etc. 37 | 38 | 2. **Explicación de estados, alfabeto, transiciones y estado de aceptación:** 39 | 40 | * Definición de términos clave: 41 | * **Estados:** Conjunto finito de estados que puede tomar el autómata. 42 | * **Alfabeto:** Conjunto finito de símbolos de entrada permitidos. 43 | * **Transiciones:** Funciones que especifican cómo el autómata cambia de un estado a otro en respuesta a la entrada. 44 | * **Estado de aceptación:** Estado (o estados) en el que el autómata termina su proceso y acepta la entrada. 45 | 46 | 47 | ****Ejercicio práctico** : Diseñar un AF para reconocer cadenas de 0s y 1s con un número par de 1s. 48 | 49 | 50 | Ejemplo de código en Python** : 51 | 52 | ``` 53 | # Definición de un AF determinista para reconocer cadenas con un número par de 1s 54 | class AFD: 55 | def __init__(self): 56 | self.Q = {'q0', 'q1'} 57 | self.sigma = {'0', '1'} 58 | self.delta = { 59 | ('q0', '0'): 'q0', 60 | ('q0', '1'): 'q1', 61 | ('q1', '0'): 'q1', 62 | ('q1', '1'): 'q0' 63 | } 64 | self.q0 = 'q0' 65 | self.F = {'q0'} 66 | 67 | def evaluar_cadena(self, cadena): 68 | estado_actual = self.q0 69 | for simbolo in cadena: 70 | estado_actual = self.delta.get((estado_actual, simbolo)) 71 | return estado_actual in self.F 72 | 73 | # Ejemplo de uso 74 | automata = AFD() 75 | print(automata.evaluar_cadena('101')) # Output: True 76 | print(automata.evaluar_cadena('111')) # Output: False 77 | 78 | ``` 79 | 80 | ## Clasificación de Autómatas Finitos 81 | 82 | 1. **AF Determinista (AFD) vs. AF No Determinista (AFND):** 83 | * **Autómata Finito Determinista (AFD):** 84 | * Definición: Un autómata finito donde, dado un estado y una entrada específica, solo hay una transición posible a otro estado. 85 | * Características: 86 | * Cada transición está definida de forma única. 87 | * Puede estar en un solo estado a la vez. 88 | * Ejemplos: Máquinas expendedoras, cajeros automáticos, etc. 89 | * **Autómata Finito No Determinista (AFND):** 90 | * Definición: Un autómata finito donde, dado un estado y una entrada específica, puede haber múltiples transiciones posibles a diferentes estados. 91 | * Características: 92 | * Puede tener múltiples transiciones para un mismo símbolo de entrada. 93 | * Puede estar en varios estados al mismo tiempo. 94 | * Ejemplos: Juegos de video con múltiples opciones de movimiento, reconocedores de lenguaje natural, etc. 95 | 96 | 97 | ### Ejemplos de ambos tipos y cómo se diferencian: 98 | 99 | #### Autómata Finito Determinista (AFD): 100 | 101 | * **Ejemplo:** Máquina expendedora de bebidas. 102 | * **Representación visual:** 103 | ``` 104 | Estado inicial: S0 105 | Estados de aceptación: {S2} 106 | 107 | Transiciones: 108 | S0 --(moneda)--> S1 109 | S1 --(seleccionar bebida)--> S2 110 | S2 --(entregar bebida)--> S0 111 | ``` 112 | 113 | * **Diferencias con AFND:** 114 | * Cada estado tiene una única transición definida para cada símbolo de entrada. 115 | * Se puede predecir exactamente el próximo estado en función del estado actual y el símbolo de entrada. 116 | 117 | #### Autómata Finito No Determinista (AFND): 118 | 119 | * **Ejemplo:** Reconocedor de números telefónicos. 120 | * **Representación visual:** 121 | 122 | ``` 123 | Estado inicial: S0 124 | Estados de aceptación: {S3} 125 | 126 | Transiciones: 127 | S0 --(dígito)--> S1 128 | S0 --(dígito)--> S2 129 | S1 --(dígito)--> S1 130 | S2 --(dígito)--> S2 131 | S1 --(dígito)--> S3 132 | S2 --(dígito)--> S3 133 | ``` 134 | 135 | Estado inicial: S0 136 | Estados de aceptación: {S3} 137 | 138 | Transiciones: 139 | S0 --(dígito)--> S1 140 | S0 --(dígito)--> S2 141 | S1 --(dígito)--> S1 142 | S2 --(dígito)--> S2 143 | S1 --(dígito)--> S3 144 | S2 --(dígito)--> S3 145 | 146 | 147 | * **Diferencias con AFD:** 148 | * Puede haber múltiples transiciones para un mismo símbolo de entrada desde un estado dado. 149 | * Puede estar en varios estados simultáneamente en respuesta a una entrada. 150 | 151 | ### Comparación de comportamiento: 152 | 153 | * **Transiciones:** 154 | * AFD: Cada transición está definida de forma única. 155 | * AFND: Puede haber múltiples transiciones para un mismo símbolo de entrada desde un estado dado. 156 | * **Estados alcanzables:** 157 | * AFD: Dado un estado y un símbolo de entrada, hay exactamente un estado al que se puede mover. 158 | * AFND: Puede haber múltiples estados a los que se puede mover desde un estado dado con un mismo símbolo de entrada. 159 | * **Complejidad de diseño:** 160 | * AFD: Suele ser más simple de diseñar y entender debido a la unicidad de las transiciones. 161 | * AFND: Puede ser más complejo debido a la posibilidad de múltiples transiciones y estados alcanzables. 162 | 163 | ### Situaciones preferibles: 164 | 165 | * **AFD:** 166 | * Situaciones donde se requiera claridad y simplicidad en el diseño del autómata. 167 | * Cuando la especificación del problema permite una única transición para cada símbolo de entrada desde cada estado. 168 | * **AFND:** 169 | * Situaciones donde se necesite modelar comportamientos no deterministas o ambiguos. 170 | * Problemas donde la complejidad del diseño se vea reducida mediante la presencia de múltiples transiciones o estados alcanzables. 171 | 172 | En resumen, los autómatas finitos deterministas (AFD) tienen transiciones únicas y son más simples de diseñar, mientras que los autómatas finitos no deterministas (AFND) pueden tener múltiples transiciones y estados alcanzables, lo que los hace más expresivos pero potencialmente más complejos. La elección entre AFD y AFND depende de la naturaleza del problema y los requisitos de diseño específicos. 173 | --------------------------------------------------------------------------------