Introducción al desarrollo de intérpretes y lenguajes de programación

1

Construcción de Intérpretes en Python desde Cero

2

Programación en Platzi: Compiladores e Intérpretes en Español

Construcción del lexer o tokenizador

3

Análisis Léxico: Creación de Tokens para Léxers

4

Tipos de Datos y Tokens en Python

5

Programación: Construcción de Lexer con Test-Driven Development

6

Pruebas Regulares en Python: Implementación de un Lexer

7

Tokens y Palabras Clave en Python: Gestión Avanzada

8

Tokens y Reconocimiento de Funciones en Lexers

9

Implementación de Operadores en Lexer de Platzi

10

Operadores Lógicos en Lenguajes de Programación

11

REPL en Python: Crear, Ejecutar y Probar Lexer Básico

Construcción del parser o analizador sintáctico

12

Construcción de un AST para Lenguaje Platzi

13

Creación de Nodos Abstractos para AST en Python

14

Parser de Lenguaje: Creación de Nodos AST en Python

15

Parseo de Let Statements en Lenguaje Platzi

16

Funciones de Tokenización en Lenguaje de Programación

17

Errores comunes al programar en Python

18

Base de Datos Relacional: Diseño y Consultas Básicas

19

Parsing con Pratt: Manejo de Precedencia y Asociatividad

20

Pruebas de AST para parsers en Python

21

Parseo de Expresiones con Pratt Parsing en Python

22

Parseo de Identificadores en AST Python

23

Parseo de Identificadores: Expresiones en PlatziLang

24

Parseo de Enteros en Python: Implementación y Pruebas

25

Operadores Prefijo en Parsers: Diseño e Implementación

26

Operadores InFix: Implementación y Pruebas en Python

27

Parseo de Operadores InFix en Python

28

Expresiones Booleanas en Lenguaje Platzi: Implementación y Testeo

29

"Precedencia de Operadores en Python"

30

Parentesis en Expresiones Matemáticas en Python

31

Tipos de Datos en el Lenguaje de Programación Platzi

32

Programación de Parsers en Lenguajes de Programación

33

Parsiendo Funciones en Lenguaje Platzi: Creación y Pruebas de Nodos AST

34

Programación Funcional: Construcción y Parsing en Python

35

Parentesis: Uso y Función en Llamadas a Procedimientos

36

Precedencia de Llamadas en Parsers: Implementación en AST

37

Parseo de Expresiones en LET y RETURN Statements

38

Construcción del REPL en Python: Lexer y Parser en Sintaxis Abstracta

Evaluación o análisis semántico

39

Análisis Semántico en Lenguajes de Programación

40

Evaluación de Código: Intérpretes vs Compiladores

41

Representación de Objetos en Python: Enteros, Booleanos y Null

42

Evaluación de enteros en Python: Crear y probar evaluador recursivo

43

Diseño de Patrones Singleton en Python

44

Semántica de Prefijos en Lenguajes de Programación

45

Evaluación de Expresiones Infix en Python

46

Condicionales en Lenguajes de Programación

47

Evaluación del Statement Return en Python

48

Manejo de Errores Semánticos en Lenguajes de Programación

49

Declaración de Variables y Ambientes en Lenguajes de Programación

50

Entornos y Cláusulas en Python: Manejo y Error Handling Avanzado

51

Declaración de Procedimientos en Lenguaje Platzi

52

Implementación y uso de closures en Platzi Programming

Mejora del intérprete

53

Implementación de strings en un intérprete Python

54

Operaciones en Strings: Concatenación y Comparación

55

Construcción de Funciones Built-in en Python

56

Función Longitud en Lenguaje Platzi

Siguientes pasos

57

Creación de Listas y Diccionarios en Python Avanzado

58

Construcción de intérpretes con Python desde cero

Crea una cuenta o inicia sesión

¡Continúa aprendiendo sin ningún costo! Únete y comienza a potenciar tu carrera

Programación: Construcción de Lexer con Test-Driven Development

5/58
Recursos

¿Cómo iniciar la construcción de un lexer y los primeros tests?

Desarrollar un lexer efectivo es un arte que requiere paciencia y atención al detalle. Al iniciar este proceso, es fundamental adoptar el enfoque de desarrollo guiado por pruebas (Test-Driven Development, TDD). Esto implica escribir las pruebas antes del código y es una práctica que ayudará a garantizar que tu código funcione como esperas. En esta sesión, vamos a enfocar nuestros primeros tests en tokens ilegales, operadores de un solo carácter y el token EOF (End of File) que nos señala el final del archivo. También, nos enfrentaremos al reto de los delimiters.

Los métodos clave que implementaremos en nuestro lexer incluyen:

  • next_token: nuestro principal punto de interacción con el lexer, que continuamente nos proporcionará el siguiente token.
  • _read_character: una función privada para leer cada carácter del texto fuente.

¿Cuál es el proceso para escribir nuestros tests?

Los tests actúan como una guía y nos ayudan a verificar continuamente que el código esté en el camino correcto. Aquí se describen los pasos esenciales para escribir tu primer test:

  1. Configuración del entorno: Asegúrate de estar en un branch correcto para el desarrollo, como "building del lexer número 1". Corre mypy y nose para verificar que no hay problemas en tu entorno.

  2. Estructura del proyecto: Crea un archivo __init__.py dentro de tus carpetas para que mypy y nose puedan reconocer los paquetes. Organiza tu proyecto de manera que todas las pruebas residan en una carpeta test.

  3. Escritura del test: Abre un editor de texto para Lexertest y comienza importando TestCase desde unittest. Luego, crea la clase LexerTest que extiende TestCase. Aquí es donde se definirá cada test.

  4. Definir el test para tokens ilegales: Por ejemplo, define caracteres ilegales como !, ¿, y @. Inicializa un lexer y usa una lista para almacenar los tokens devueltos tras llamar a next_token.

  5. Asserts y errores: Asegúrate de usar assertEqual para verificar que la lista de tokens devueltos coincide con la esperada. Los errores en este punto son valiosos, te guiarán sobre qué ha fallado y cómo solucionarlo.

¿Cómo implementamos las funciones clave del lexer?

Con los tests en su lugar, es momento de codificar las funciones necesarias. Aquí se desglosan los pasos para implementar los métodos de nuestro lexer:

  • Inicializar el lexer: Empieza creando una clase Lexer con un constructor que recibe un source. Define también una variable privada _source para almacenar este valor.

  • Implementar next_token y _read_character:

    • next_token: Este método regresará un token. Inicialmente, puede devolver un token ilegal con un carácter vacío hasta que se implemente la lógica adecuada.
    • _read_character: Esta función se encargará de avanzar en el texto fuente y deberá actualizar las posiciones de las variables internas.
class Lexer:
    def __init__(self, source: str) -> None:
        self._source = source
        self._read_position = 0
        self._position = 0
        self._character = ''
        self._read_character()

    def next_token(self) -> Token:
        token = Token(TokenType.ILLEGAL, self._character)
        self._read_character()
        return token

    def _read_character(self) -> None:
        if self._read_position >= len(self._source):
            self._character = ''
        else:
            self._character = self._source[self._read_position]
        self._position = self._read_position
        self._read_position += 1

¿Cómo resolver errores y avanzar en el desarrollo?

El desarrollo guiado por pruebas es un ciclo de escribir fallos de tests, solucionarlos y mejorar continuamente. Aquí algunas recomendaciones al enfrentar errores:

  • Leer errores atentamente: Estos mensajes a menudo te dicen exactamente dónde está el problema y cómo corregirlo.
  • Resolver errores gradualmente: Correcciones pequeñas y frecuentes son preferibles a cambios drásticos que pueden introducir más errores.
  • Considerar los errores como aliados: Permiten identificar partes del código que no funcionan como se esperaba y son esenciales para mejorar tu producto final.

Las pruebas y los errores son parte inseparable del proceso de desarrollo, y aprender a manejarlos te convertirá en un mejor desarrollador. ¡Con perseverancia, cada error es una oportunidad de aprendizaje que te acerca más a una solución robusta y optimizada!

Aportes 15

Preguntas 4

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Efectivamente los errores fueron mis amigos jaja, tuve otros errores porque había escrito mal una sintaxis en Python, y me decía: “Error en la línea X” y fui a ver la línea X y así encontré mis errores 😄
.
Apenas estamos iniciando pero ando con hype jajaja, me gusta el TDD. Por cierto, algo curioso es que, por la forma en la que se leen los tokens ilegales, el orden en el que los comparamos en el expected_tokens debe ser el mismo que el orden en el que pusimos los tokens en source, de lo contrario el assert fallará aunque los tokens sí sean ilegales simplemente por el orden

A mí los test no me fallaban, solo me decía Ran 0 tests in 0.007s (se corrieron cero pruebas en 0.007 segundos) y al final lo solucioné usando el comando:

mypy . && py -m unittest discover  -p "*_test.py"

en vez de

mypy . && nosetests

A ver, entendamos lo que tenemos por acá:

def test_ilegal(self) -> None:
        source: str = '!¿@'
        lexer: Lexer = Lexer(source)
        tokens: List[Token] = []

        for i in range(len(source)):
            tokens.append(lexer.next_token())

        expected_tokens: List[Token] = [
            Token(TokenType.ILLEGAL, '!'),
            Token(TokenType.ILLEGAL, '¿'),
            Token(TokenType.ILLEGAL, '@')
        ]

        self.assertEquals(tokens, expected_tokens)

Aquí tenemos un test en el que estamos cargando una lista con caracteres inválidos para nuestro lenguaje. Luego de esto creamos una lista de los tokens esperados, en este caso queremos que el Lexer devuelva que los tres caracteres son ilegales como TokenType.ILLEGAL Para hacer esto alimentamos el lexer con la línea de texto que contiene los caracteres ilegales. Esta línea es revisada por el lexer en next_token Method validando que el carácter sea ilegal y devolviendo un TokenType.ILLEGAL Sin embargo, debemos notar que en este punto el Lexer siempre va a devolver Token.ILLEGAL para lo que sea que lea, podemos meterle cualquier carácter alfanumérico que siempre va a devolver Token.ILLEGAL ya que aún no estamos haciendo ninguna validación.

def next_token(self) -> Token:
        token = Token(TokenType.ILLEGAL, self._character)
        self._read_character()
        return token

En el método next_token devolvemos siempre Tokens Ilegales por cada carácter recibido en la línea.

def _read_character(self) -> None:
        if self._read_position >= len(self._source):
            self._character = ''
        else:
            self._character = self._source[self._read_position]
        self._position = self._read_position
        self._read_position += 1

Solamente está moviendo la lectura un carácter hacia adelante de la lista recibida en la línea de texto.

  • Rescatamos acá un par de cosas, los Lexer van a moverse linea a linea por cada archivo de código fuente.

  • Los token nos van a permitir usar los Enum para identificar el tipo de elemento que tenemos en cada línea

  • Tendremos que encontrar una forma de pasear o validar palabras más allá de caracteres simples, es como veíamos en la clase pasada lo que haremos con los espacios en blanco.

Para aquellos que “nosetests” les de algún error como ‘AttributeError’, es porque “nose” ya no es mantenido, yo he instalado nose2. Lo usarían así

mypy . && nose2

Tip: Desde python 3.9 es posible utilizar list[Token] para no tener que importar List desde typing.

Si no les funciona mypy . && nosetest y ya intentaron las soluciones de la sección de aportes, instalen nose2

requirements.txt

nose2
mypy==0.782

recuerden renombrar lexer_test.py por test_lexer.py

Y ya solo ejecutan

mypy . && nose2
para windows podemos usar `mypy .; nosetests`

Nota mental, para que los tests corran hay que estar en la carpeta raíz que contiene la carpeta lpp. De lo contrario se muestra un error como este:

. is not a valid Python package name

Tengo que darle un par de vueltas más. Complicado el tema

**nose** esta deprecado, recomiendo usar y instalar **pynose**, el comando sería: ` mypy . && pynose`
Si les da **error**: `"LexerTest" has no attribute "assertEquals" [attr-defined]` Es porque ahora se llama `assertEqual` **Fuente:** Click derecho ir a la definition, y les muestra algo asi: <https://github.com/python/typeshed/blob/119b204ed18ca3cf4892a95aabab6b3027151ab8/stdlib/unittest/case.pyi#L133C9-L133C20>
recuerden que la versión actual es python 3.9

Para aquellos que esten en windows como yo, se les sera un poco mas dificil, en especial la parte de concatenar los comandos, pero lo solucione usando la terminal de windows 10 y dando enters, otra cosa que tuve que hacer fue instalar el nose2 y renombrar el archivo a test_lexer.py

El comando final quedaria como:

mypy.
nose2

Tienen que dejar el salto de linea

Chic@s, si no les funciona el test intenten con este code.

$ mypy . && nosetests test/*_test.py

Muy cool el TDD!!!