Manejo de Ambientes y Variables en Lenguajes de Programación

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

Lograr que un lenguaje de programación propio declare variables y las recuerde entre líneas es un hito importante. Aquí se explica cómo funciona el ambiente (el diccionario interno que almacena nombres y valores), cómo se implementan los nodos let statement e identifier en el evaluador, y cómo se modifica el REPL para soportar múltiples líneas.

¿Cómo funciona el ambiente en un lenguaje de programación?

El ambiente es un diccionario que guarda llaves (los nombres de las variables) y valores (el resultado de evaluar las expresiones asignadas). Así funcionan todos los lenguajes de programación internamente: mantienen una estructura donde puedes consultar el valor asociado a un nombre en cualquier punto del programa [0:12].

Este concepto se vuelve más interesante cuando existen múltiples ambientes. Las funciones generan su propio ambiente, y funciones dentro de funciones generan los suyos. Esto se conoce como closures [0:40]. Está directamente relacionado con los scopes: dependiendo del scope en el que te encuentres, tienes acceso a ciertos valores del ambiente. Ahora, al construir un intérprete, se comprende cómo opera esto de manera interna [0:52].

¿Cómo se implementa la declaración de variables con let statement?

El primer paso es crear un nuevo tipo de error llamado unknown identifier [1:38]. Este error se lanza cuando se intenta acceder a un identificador que no existe en el ambiente.

Después, se trabaja con dos nodos del AST:

¿Qué hace el nodo let statement?

Cuando el evaluador encuentra un nodo de tipo ast.LetStatement, realiza lo siguiente [1:56]:

  • Hace un cast del nodo a LetStatement.
  • Verifica que el valor interno no sea None.
  • Evalúa recursivamente la expresión asignada, pasándole el ambiente.
  • Guarda en el ambiente el nombre de la variable con el valor ya evaluado.

Esto permite evaluar expresiones complejas. Por ejemplo, si escribes variable a = 5 * 5 * 5, la expresión 5 * 5 * 5 se resuelve recursivamente y el resultado final se almacena en el ambiente con la llave a [2:44].

¿Cómo se resuelve un identifier en el evaluador?

Cuando el evaluador encuentra un nodo ast.Identifier, necesita obtener el valor previamente guardado en el ambiente [3:09]. La función evaluate_identifier es sencilla:

  • Recibe el nodo y el ambiente.
  • Intenta acceder al diccionario usando el nombre del identificador como llave.
  • Si la llave no existe, se utiliza un enfoque pythónico: pedir perdón y no permiso. Se captura la excepción KeyError y se retorna un error con el mensaje unknown identifier [3:38].

python def evaluate_identifier(node: ast.Identifier, env: Environment) -> Object: try: return env[node.value] except KeyError: return new_error(UNKNOWN_IDENTIFIER, [node.value])

Con estas dos modificaciones, los tests comienzan a pasar correctamente [4:18].

¿Cómo hacer que el REPL soporte múltiples líneas?

Al probar el REPL, se puede declarar variable a = 5, variable b = 10, luego variable c = a + b y evaluar c, obteniendo 15 [4:38]. Sin embargo, cada línea genera un programa nuevo, por lo que al evaluar a en una línea separada, el intérprete dice que no encontró el identificador.

La solución es acumular todo lo escaneado en una lista llamada scant [5:10]:

  • Cada input del usuario se agrega a esta lista.
  • En lugar de pasar solo la línea actual al lexer, se unen todos los statements previos.
  • Así el REPL recuerda las declaraciones anteriores.

Con esta modificación, el REPL funciona como se espera [5:30]:

  • variable a = 5.
  • variable b = 10.
  • a + b evalúa a 15.
  • a * b evalúa a 50.
  • Se pueden guardar resultados en nuevas variables y seguir operando.

El lenguaje ya cuenta con variables, condicionales, operaciones aritméticas, comparaciones y booleanos. La siguiente pieza fundamental son las funciones, necesarias para reutilizar código en un lenguaje procedimental [6:15].

¿Ya lograste que tu intérprete declare variables? Comparte tu experiencia y cuéntanos cómo va tu implementación.

      Manejo de Ambientes y Variables en Lenguajes de Programación