Pruebas de AST para Let y Return Statements en Parsers

Clase 20 de 58Curso de Creación de Lenguajes de Programación: Intérpretes

Contenido del curso

Construcción del lexer o tokenizador

Construcción del parser o analizador sintáctico

Evaluación o análisis semántico

Resumen

Cuando construimos un parser, no basta con saber que reconoce sentencias correctamente. También necesitamos verificar que la estructura de datos interna —el AST— sea exactamente la que esperamos. Esta técnica complementaria permite construir manualmente el árbol de sintaxis abstracta y compararlo con el programa que el parser debería generar, ofreciendo una visión clara de cómo se representan las instrucciones en memoria.

¿Qué significa probar el AST de forma inversa?

Hasta ahora, las pruebas del parser partían de un string con código fuente y verificaban que se generara cierta estructura. La técnica que se presenta aquí invierte ese flujo: se construye primero el AST con sus nodos y luego se compara contra el string que esa estructura debería producir [01:20].

Esto es posible porque cada nodo del AST implementa el método __str__ (dunder string), que define su representación textual. Al llamar str(program), Python recorre el árbol y devuelve el programa original reconstruido.

  • Se construye una instancia de Program con su lista de statements.
  • Se compara el resultado de str(program) con el código esperado.
  • Si no coinciden, el test falla y sabemos exactamente dónde está el problema.

¿Cómo se estructura un test para el let statement?

Para preparar el archivo de pruebas, se crea ast_test.py dentro del folder de tests [02:30]. Los imports necesarios son:

  • TestCase del módulo unittest.
  • Identifier, LetStatement y Program del módulo lpp/ast.
  • Token y TokenType del módulo lpp/token.

El programa que se quiere representar es variable mi_var = otra_variable;. Para construir este AST manualmente, el nodo raíz es un Program, que contiene una lista de statements [03:45].

¿Qué nodos componen un let statement?

Un LetStatement requiere tres elementos:

  • Un token de tipo TokenType.LET con literal "variable".
  • Un name que es un Identifier, inicializado con un token de tipo TokenType.IDENT y literal "mi_var", cuyo valor también es "mi_var".
  • Un value que en este caso es otro Identifier, con token de tipo TokenType.IDENT, literal "otra_variable" y valor "otra_variable" [05:10].

El value de un LetStatement puede ser cualquier expresión: un entero, una función o, como en este ejemplo, otro identificador. Esa flexibilidad es fundamental para representar asignaciones complejas.

¿Cómo se ejecuta la verificación?

Una vez armado el árbol, se convierte a string y se compara:

python program_str = str(program) self.assertEqual(program_str, 'variable mi_var = otra_variable;')

Si se modifica el string esperado, por ejemplo añadiendo un carácter de negación, el test falla inmediatamente, confirmando que el AST solo produce la representación exacta del programa original [07:00].

¿Cómo se ve la estructura completa del árbol?

La jerarquía resultante es clara:

  • Program → contiene una lista de statements.
    • LetStatement → tiene un token (let), un name (Identifier) y un value (expresión).
      • Identifier (name) → token de tipo IDENT, valor "mi_var".
      • Identifier (value) → token de tipo IDENT, valor "otra_variable".

Si el programa tuviera más instrucciones, simplemente se agregarían más elementos a la lista de statements. Por ejemplo, un ReturnStatement sería otro nodo dentro de esa misma lista [08:30].

Esta forma de visualizar el AST refuerza un concepto importante: el parser transforma texto plano en una estructura de datos jerárquica que luego puede ser evaluada, optimizada o transformada.

El reto propuesto consiste en implementar la prueba equivalente para un ReturnStatement, aplicando exactamente la misma lógica: construir el árbol, convertirlo a string y verificar que coincida con el código fuente esperado. Comparte tu solución y cualquier duda en los comentarios para enriquecer el proceso de aprendizaje colectivo.