Manejo de Errores Semánticos en Lenguaje Platzi

Clase 48 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 la sintaxis de un programa es correcta pero su significado no tiene sentido, aparecen los errores semánticos. Saber identificarlos y detener la ejecución a tiempo es fundamental para construir un lenguaje de programación robusto. Aquí se explica cómo implementar este mecanismo paso a paso, desde los tests hasta la propagación del error en el evaluador.

¿Qué es un error semántico y por qué difiere de un error de sintaxis?

Un error semántico ocurre cuando una expresión tiene la estructura correcta —paréntesis bien cerrados, puntos y comas en su lugar— pero carece de significado dentro del lenguaje [0:18]. Por ejemplo, expresiones como verdadero + falso o 5 + verdadero no representan operaciones válidas.

Este tipo de errores se conoce como type mismatch (discrepancia de tipos) cuando los operandos no son compatibles, o como unknown operator (operador desconocido) cuando la operación no está definida para ciertos tipos [0:42]. Es importante recordar que tú decides las reglas: si quisieras que -verdadero evaluara a falso, podrías hacerlo. Cada decisión define la semántica de tu lenguaje.

¿Cómo se diseñan los tests para errores semánticos?

La estrategia sigue el enfoque test-driven: primero se escriben los tests y después la implementación [1:38]. Se generan múltiples programas inválidos y se verifica que cada uno devuelva el mensaje de error correcto:

  • 5 + verdaderotype mismatch: entero más booleano.
  • -verdadero → operador prefijo desconocido para booleanos.
  • verdadero + falso → operador infijo desconocido.
  • verdadero - falso → operador desconocido.
  • Expresiones con * o / entre booleanos → operador desconocido.

Una característica clave es que el error detiene la ejecución [2:22]. Si una línea produce un error, las líneas siguientes no deben evaluarse. Esto se valida incluyendo expresiones válidas después del error y comprobando que nunca se alcancen.

¿Cómo se implementa la clase error?

El objeto Error es sencillo: hereda de la clase base Object e implementa los métodos type e inspect [4:28]. Su constructor recibe un mensaje como string.

python class Error(Object): def init(self, message: str): self.message = message

def type(self) -> ObjectType: return ObjectType.ERROR def inspect(self) -> str: return f"Error: {self.message}"

Además, se añade ERROR como nuevo valor en el enum ObjectType [5:02].

¿Cómo funciona la función new error y las constantes de mensajes?

Se definen tres constantes que actúan como plantillas de mensaje [5:38]:

  • TYPE_MISMATCH: discrepancia de tipos con formato tipo operador tipo.
  • UNKNOWN_PREFIX_OPERATOR: operador prefijo desconocido con formato operador tipo.
  • UNKNOWN_INFIX_OPERATOR: operador infijo desconocido con formato tipo operador tipo.

La función new_error recibe el mensaje plantilla y una lista variable de argumentos [6:30]. Utiliza unpacking (*args) para formatear el string dinámicamente:

python def new_error(message: str, *args) -> Error: return Error(message.format(*args))

Este patrón es equivalente al operador spread (...) en JavaScript: extrae todos los valores de la lista y los pasa como argumentos individuales al método format.

¿Dónde se propagan los errores dentro del evaluador?

La propagación es el paso más delicado. Cada punto donde antes se retornaba None ahora debe devolver una instancia de Error [7:10]:

  • En evaluate_prefix_expression: si el operador no es válido para el tipo, se retorna unknown prefix operator.
  • En evaluate_minus_operator_expression: si el operando no es entero, se retorna unknown prefix operator con el operador - [9:15].
  • En evaluate_integer_infix_expression: si el operador no está soportado, se retorna unknown infix operator.
  • En evaluate_infix_expression: si los tipos de ambos lados no coinciden, se retorna type mismatch [8:18].

¿Cómo se detiene la ejecución al encontrar un error?

En dos lugares estratégicos se verifica si el resultado es un error para interrumpir la evaluación [8:44]:

  • evaluate_block_statement: si el tipo del resultado es RETURN o ERROR, se deja de recorrer los statements del bloque.
  • evaluate_program: si el resultado es un error, se retorna inmediatamente sin continuar con el resto del programa.

Este comportamiento garantiza que un error semántico corte la ejecución de forma limpia, sin evaluar código posterior que ya no tendría sentido.

Si quieres llevar esto más lejos, un buen reto es modificar el objeto Error para que incluya el número de línea donde ocurrió el problema [10:18]. Para lograrlo, necesitarías capturar la posición en cada token durante el análisis léxico y pasar esa información al error. ¿Te animas a implementarlo?