Desarrollo de un AST en Python: Creación de la Clase Programa

Clase 14 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

Construir un lenguaje de programación desde cero exige una pieza fundamental: el parser. Aquí se muestra cómo dar el primer paso concreto, creando el nodo raíz del Abstract Syntax Tree (AST) llamado Program y escribiendo el primer unit test que valida su existencia. Todo el proceso sigue la metodología de test-driven development (TDD): primero el test, después la implementación mínima para que pase.

¿Qué representa el nodo Program dentro del AST?

El AST se compone de dos grandes familias de nodos: expresiones y statements. El nodo Program es el nodo raíz que contiene una lista de statements; es el punto de entrada de todo el árbol. Sin él, no hay estructura donde almacenar lo que el parser interprete.

Para definirlo se crea una clase Program que hereda de ASTNode [1:40]:

python from typing import List

class Program(ASTNode):

def __init__(self, statements: List[Statement]) -> None: self.statements = statements def token_literal(self) -> str: if len(self.statements) > 0: return self.statements[0].token_literal() return '' def __str__(self) -> str: out: List[str] = [] for statement in self.statements: out.append(str(statement)) return ''.join(out)
  • token_literal devuelve el token literal del primer statement; sirve exclusivamente para debugging.
  • El dunder method __str__ concatena todos los statements convertidos a string, permitiendo visualizar el programa completo como texto.
  • Se importa List desde typing porque la clase necesita tipar la lista de statements.

¿Cómo se escribe el primer test del parser con TDD?

Antes de implementar cualquier lógica, se crean dos archivos: parser.py y parser_test.py dentro de la carpeta de tests [0:34]. El test se diseña imaginando la interfaz ideal, sin que exista aún el código que la soporte.

python from unittest import TestCase from lpp.ast import Program from lpp.lexer import Lexer from lpp.parser import Parser

class ParserTest(TestCase):

def test_parse_program(self) -> None: source: str = 'variable x = 5;' lexer: Lexer = Lexer(source) parser: Parser = Parser(lexer) program: Program = parser.parse_program() self.assertIsNotNone(program) self.assertIsInstance(program, Program)

Las dos assertions validan que:

  • El resultado no sea None.
  • El resultado sea una instancia de Program.

Al ejecutar este test por primera vez, falla indicando que ni Parser ni Program existen [1:10]. Cada error nuevo señala el siguiente paso a implementar.

¿Por qué los errores progresivos son una buena señal?

En TDD, conforme cambia el mensaje de error, significa que avanzas [4:20]. Primero el test dice que Parser no existe; luego, que parse_program no está definido; finalmente, que el programa retornado es None. Cada iteración acerca el código a una solución funcional.

¿Cómo se implementa el parser mínimo para que el test pase?

El archivo parser.py arranca con la estructura más simple posible [3:30]:

python from lpp.ast import Program from lpp.lexer import Lexer

class Parser:

def __init__(self, lexer: Lexer) -> None: self._lexer = lexer def parse_program(self) -> Program: program: Program = Program(statements=[]) return program
  • El constructor recibe un Lexer y lo almacena en una variable privada _lexer.
  • parse_program retorna un Program con una lista vacía de statements.

Esta implementación es deliberadamente mínima. Su único objetivo es hacer pasar el test actual [5:00]. Más adelante, al agregar tests para el let statement y otros nodos, parse_program crecerá incorporando lógica real de parseo.

¿Qué ventaja ofrece esta suite de tests a futuro?

Cada test funciona como un colchón de seguridad. Si en algún momento parse_program deja de retornar un Program válido —por un cambio accidental o un refactor— el test fallará de inmediato [5:20]. Esto permite detectar regresiones antes de que se propaguen.

Si tienes dudas sobre la estructura del AST o la implementación del parser, comparte tus preguntas en los comentarios para que la comunidad pueda apoyarte.