Declaración de Funciones en Lenguaje de Programación Platzi

Clase 51 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 implica dar vida a cada elemento que usamos a diario sin pensar: variables, expresiones y, por supuesto, funciones. En este punto del desarrollo del intérprete, ya se pueden declarar variables y evaluar expresiones; el siguiente paso natural es permitir que el usuario defina sus propias funciones mediante el keyword procedimiento.

¿Cuáles son los tres componentes de una función?

Cada función dentro del intérprete se compone de tres piezas fundamentales [0:42]:

  • Parámetros: una lista de identificadores (por ejemplo x, y, z) que actúan como entradas.
  • Cuerpo: un bloque de statements, es decir, un mini programa que la función ejecuta y del cual puede regresar un valor.
  • Ambiente (environment): el contexto que determina a qué variables tiene acceso la función y cuáles son sus valores actuales.

El ambiente es especialmente relevante porque, al momento de llamar a la función, los parámetros abstractos como x se transforman en valores concretos. Esto abre la puerta a crear una jerarquía de ambientes donde cada función puede consultar si tiene acceso a determinada variable o no.

¿Por qué las funciones son objetos dentro del lenguaje?

Todos los elementos dentro de un lenguaje de programación son, en realidad, objetos internos del propio lenguaje [1:06]. Esto no es exclusivo de los intérpretes; sucede también en HTML: cuando escribes <div>, se crea una instancia de HTMLDivElement; un <a> genera un AnchorHTMLElement. Siempre que se construye un árbol —ya sea un AST o un DOM— existe una jerarquía de objetos con la que se puede trabajar.

Por eso, para representar funciones dentro del intérprete se implementa un objeto función (function object) que hereda de la clase base Object.

¿Cómo se implementa el objeto función en código?

Dentro del módulo object se realizan varios cambios [4:10]:

  • Se importan List de typing y los nodos Block e Identifier del AST.
  • Se agrega un nuevo valor al enum ObjectType llamado FUNCTION.
  • Se crea la clase Function que extiende Object.

El constructor recibe los tres componentes mencionados:

python def init(self, parameters: List[Identifier], body: Block, env: Environment): self.parameters = parameters self.body = body self.env = env

Dos métodos obligatorios completan la implementación:

  • type() regresa ObjectType.FUNCTION.
  • inspect() construye una representación en string de la función, uniendo los parámetros con comas y mostrando el cuerpo entre corchetes con saltos de línea, tal como se escribiría en código fuente.

python def inspect(self) -> str: params = ', '.join([str(p) for p in self.parameters]) return f'procedimiento({params}) {{\n{str(self.body)}\n}}'

¿Cómo se integra la función en el evaluador?

Dentro del evaluador se importa el nuevo Function y se añade un nuevo bloque elif en la cascada de condiciones de la función evaluate [7:07]. Python no cuenta con switch statements, así que se utiliza una cadena de elif:

python elif isinstance(node, ast.Function): assert node.body is not None return obj.Function(node.parameters, node.body, env)

Este fragmento verifica que el cuerpo no sea None, toma los parámetros y el ambiente actual, y devuelve el objeto función listo para ser utilizado.

¿Cómo se valida con test driven development?

Siguiendo la metodología TDD (test driven development), primero se escribe el test test_function_evaluation [2:50]. El test declara un programa con procedimiento(x) { x + 2 }, lo evalúa y verifica:

  • Que el resultado sea una instancia de Function.
  • Que la lista de parámetros tenga exactamente un elemento y que ese elemento sea x.
  • Que al convertir el cuerpo a string se obtenga (x + 2).

Al correr los tests por primera vez, fallan porque el objeto función aún no existe. Después de implementarlo y corregir un pequeño error de nombre (parameters en lugar de params), los tests pasan correctamente [8:12].

Con la declaración de funciones funcionando, el intérprete del lenguaje Platzi está muy cerca de su versión inicial. Lo que queda pendiente es implementar las llamadas a funciones: ligar los valores a los parámetros, construir el ambiente correcto y evaluar el cuerpo. Si ya llegaste hasta aquí construyendo tu propio lenguaje, prueba el REPL y comparte qué tal se siente declarar funciones en un lenguaje que tú mismo creaste.