# ============================================================================== # tesseract_errors.py — Sistema de Errores Completo para Tesseract # ============================================================================== # Uso: # from tesseract_errors import ( # TesseractError, LangError, set_language, # RangeBoundsError, TypeMismatchError, ... # ) # # set_language("es") o set_language("en") — por defecto "es" # El --lang flag se maneja desde el archivo de parámetros CLI (externo a este) # ============================================================================== import unicodedata # ────────────────────────────────────────────────────────────────────────────── # I18N — Mensajes bilingüe # ────────────────────────────────────────────────────────────────────────────── _LANG = "es" def set_language(lang: str): global _LANG if lang not in ("es", "en"): raise ValueError(f"Language '{lang}' not supported. Use 'es' or 'en'.") _LANG = lang def get_language() -> str: return _LANG _MESSAGES: dict = { # ── Léxico / Sintaxis ────────────────────────────────────────────────────── "lex.invalid_char": { "es": "carácter inválido '{char}' en la línea {line}", "en": "invalid character '{char}' at line {line}", }, "lex.unterminated_string": { "es": "cadena de texto sin cerrar desde la línea {line}", "en": "unterminated string starting at line {line}", }, "syntax.unexpected_token": { "es": "token inesperado '{token}', se esperaba '{expected}'", "en": "unexpected token '{token}', expected '{expected}'", }, "syntax.missing_token": { "es": "se esperaba '{expected}' pero se encontró fin de archivo", "en": "expected '{expected}' but reached end of file", }, "indent.expected": { "es": "se esperaba una indentación mayor aquí", "en": "expected a deeper indentation here", }, "indent.unexpected": { "es": "indentación inesperada", "en": "unexpected indentation", }, "block.unclosed": { "es": "bloque abierto con '{open}' nunca fue cerrado", "en": "block opened with '{open}' was never closed", }, "block.orphan_close": { "es": "cierre de bloque '{close}' sin bloque abierto correspondiente", "en": "closing '{close}' without a matching open block", }, # ── Nombres y Scope ──────────────────────────────────────────────────────── "name.undefined": { "es": "'{name}' no está definido", "en": "'{name}' is not defined", }, "name.redeclaration": { "es": "'{name}' ya fue declarado en este scope", "en": "'{name}' was already declared in this scope", }, "name.shadow_warning": { "es": "'{name}' oculta una variable del scope padre", "en": "'{name}' shadows a variable from the outer scope", }, "scope.invalid_access": { "es": "no se puede acceder a '{name}' desde este scope", "en": "cannot access '{name}' from this scope", }, "import.not_found": { "es": "módulo '{module}' no fue encontrado", "en": "module '{module}' was not found", }, "import.member_missing": { "es": "'{member}' no existe en el módulo '{module}'", "en": "'{member}' does not exist in module '{module}'", }, # ── Tipos ───────────────────────────────────────────────────────────────── "type.mismatch": { "es": "se esperaba '{expected}', se recibió '{got}'", "en": "expected '{expected}', got '{got}'", }, "type.inference_failed": { "es": "no se pudo inferir el tipo de '{name}'", "en": "could not infer the type of '{name}'", }, "type.explicit_conflict": { "es": "el tipo declarado '{declared}' no coincide con el valor '{got}'", "en": "declared type '{declared}' does not match value type '{got}'", }, "type.return_mismatch": { "es": "la función '{func}' debe retornar '{expected}', retornó '{got}'", "en": "function '{func}' must return '{expected}', returned '{got}'", }, "type.null_not_allowed": { "es": "el tipo '{type}' no permite valores null", "en": "type '{type}' does not allow null values", }, "type.unsupported_cast": { "es": "no se puede convertir '{from_type}' a '{to_type}'", "en": "cannot cast '{from_type}' to '{to_type}'", }, # ── Variables ───────────────────────────────────────────────────────────── "var.const_reassign": { "es": "'{name}' es una constante y no puede reasignarse", "en": "'{name}' is a constant and cannot be reassigned", }, "var.uninitialized": { "es": "'{name}' fue declarado pero nunca inicializado", "en": "'{name}' was declared but never initialized", }, "var.invalid_assignment": { "es": "no se puede asignar '{value}' a la variable '{name}'", "en": "cannot assign '{value}' to variable '{name}'", }, # ── Funciones ───────────────────────────────────────────────────────────── "func.undefined": { "es": "la función '{name}' no está definida", "en": "function '{name}' is not defined", }, "func.arity": { "es": "'{name}' espera {expected} argumento(s), se recibieron {got}", "en": "'{name}' expects {expected} argument(s), got {got}", }, "func.arg_type": { "es": "argumento '{param}' en '{func}': se esperaba '{expected}', se recibió '{got}'", "en": "argument '{param}' in '{func}': expected '{expected}', got '{got}'", }, "func.return_outside": { "es": "'return' usado fuera de una función", "en": "'return' used outside of a function", }, "func.recursion_limit": { "es": "límite de recursión alcanzado en '{name}' (máximo: {limit})", "en": "recursion limit reached in '{name}' (maximum: {limit})", }, "func.missing_return": { "es": "la función '{name}' tiene tipo de retorno '{type}' pero no siempre retorna un valor", "en": "function '{name}' has return type '{type}' but does not always return a value", }, "func.return_mismatch": { "es": "la función '{name}' debe retornar '{expected}', pero retornó '{got}'", "en": "function '{name}' must return '{expected}', but returned '{got}'", }, # ── Operaciones ─────────────────────────────────────────────────────────── "op.division_zero": { "es": "no se puede dividir entre cero", "en": "cannot divide by zero", }, "op.invalid_operand": { "es": "operando '{got}' inválido para el operador '{op}'", "en": "invalid operand '{got}' for operator '{op}'", }, "op.type_mismatch": { "es": "operación '{op}' no soportada entre '{left}' y '{right}'", "en": "operation '{op}' not supported between '{left}' and '{right}'", }, "op.overflow": { "es": "el resultado de la operación excede el límite del tipo '{type}'", "en": "operation result exceeds the limit for type '{type}'", }, "op.bitwise_invalid": { "es": "operación bit a bit '{op}' no aplicable a '{type}'", "en": "bitwise operation '{op}' not applicable to '{type}'", }, "op.modulo_zero": { "es": "módulo por cero no está permitido", "en": "modulo by zero is not allowed", }, # ── Condiciones / Control de Flujo ──────────────────────────────────────── "cond.not_bool": { "es": "la condición debe evaluar a booleano, se recibió '{got}'", "en": "condition must evaluate to boolean, got '{got}'", }, "flow.break_outside": { "es": "'break' usado fuera de un bucle o switch", "en": "'break' used outside of a loop or switch", }, "flow.continue_outside": { "es": "'continue' usado fuera de un bucle", "en": "'continue' used outside of a loop", }, "flow.unreachable": { "es": "código inalcanzable detectado después de '{stmt}'", "en": "unreachable code detected after '{stmt}'", }, # ── Colecciones / Indexing ───────────────────────────────────────────────── "index.out_of_range": { "es": "índice {index} fuera de rango, la colección tiene longitud {length}", "en": "index {index} out of range, collection length is {length}", }, "index.negative": { "es": "índice negativo {index} no está permitido en este contexto", "en": "negative index {index} is not allowed in this context", }, "key.not_found": { "es": "la clave '{key}' no existe en el diccionario", "en": "key '{key}' does not exist in the dictionary", }, "iter.not_iterable": { "es": "'{type}' no es iterable", "en": "'{type}' is not iterable", }, "collection.limit_exceeded": { "es": "la colección '{name}' tiene un límite de {limit} elemento(s)", "en": "collection '{name}' has a limit of {limit} element(s)", }, # ── Acceso Profundo arr[0:2:1] ──────────────────────────────────────────── "deep.out_of_range": { "es": "acceso profundo falló en nivel {depth}: índice {index} fuera de rango (longitud: {length})", "en": "deep access failed at depth {depth}: index {index} out of range (length: {length})", }, "deep.key_not_found": { "es": "acceso profundo falló en nivel {depth}: clave '{key}' no existe", "en": "deep access failed at depth {depth}: key '{key}' does not exist", }, "deep.not_collection": { "es": "acceso profundo falló en nivel {depth}: se esperaba una colección, se encontró '{got}'", "en": "deep access failed at depth {depth}: expected a collection, found '{got}'", }, "deep.null_traversal": { "es": "acceso profundo falló en nivel {depth}: el valor es null, no se puede continuar", "en": "deep access failed at depth {depth}: value is null, cannot continue", }, "deep.path_malformed": { "es": "la ruta de acceso profundo '{path}' está mal formada", "en": "deep access path '{path}' is malformed", }, "deep.type_mismatch": { "es": "acceso profundo en nivel {depth}: se usó índice de tipo '{got}', se esperaba '{expected}'", "en": "deep access at depth {depth}: used index of type '{got}', expected '{expected}'", }, # ── Rangos ──────────────────────────────────────────────────────────────── "range.bounds": { "es": "rango inválido: el inicio '{start}' no puede ser mayor que el fin '{end}'", "en": "invalid range: start '{start}' cannot be greater than end '{end}'", }, "range.type_incompatible": { "es": "rango entre '{left}' y '{right}' no está permitido (tipos incompatibles)", "en": "range between '{left}' and '{right}' is not allowed (incompatible types)", }, "range.float_precision": { "es": "valor flotante '{value}' excede 1 decimal permitido en rangos (ej: 0.5..1.5)", "en": "float value '{value}' exceeds 1 decimal allowed in ranges (e.g. 0.5..1.5)", }, "range.unicode_invalid": { "es": "código unicode '{code}' no existe o no está asignado en el estándar Unicode", "en": "unicode code '{code}' does not exist or is unassigned in the Unicode standard", }, "range.unicode_order": { "es": "rango string inválido: '{start}' (U+{start_cp}) es mayor que '{end}' (U+{end_cp})", "en": "invalid string range: '{start}' (U+{start_cp}) is greater than '{end}' (U+{end_cp})", }, "range.unicode_case_mix": { "es": "rango string no puede mezclar mayúsculas y minúsculas ('{start}'..'{end}')", "en": "string range cannot mix uppercase and lowercase ('{start}'..'{end}')", }, "range.null_mixed": { "es": "solo se permite null..null, no se puede mezclar null con otro tipo", "en": "only null..null is allowed, cannot mix null with another type", }, "range.empty": { "es": "el rango '{start}'..'{end}' produce una secuencia vacía", "en": "range '{start}'..'{end}' produces an empty sequence", }, # ── OOP / Clases ────────────────────────────────────────────────────────── "class.undefined": { "es": "la clase '{name}' no está declarada", "en": "class '{name}' is not declared", }, "class.attribute_missing": { "es": "el atributo '{attr}' no existe en la clase '{cls}'", "en": "attribute '{attr}' does not exist in class '{cls}'", }, "class.method_missing": { "es": "el método '{method}' no existe en '{cls}' ni en su jerarquía", "en": "method '{method}' does not exist in '{cls}' or its hierarchy", }, "class.inheritance_invalid": { "es": "'{child}' no puede extender '{parent}': herencia inválida", "en": "'{child}' cannot extend '{parent}': invalid inheritance", }, "class.abstract_instantiation": { "es": "no se puede instanciar la clase abstracta '{name}'", "en": "cannot instantiate abstract class '{name}'", }, "class.final_extension": { "es": "no se puede extender la clase final '{name}'", "en": "cannot extend final class '{name}'", }, "class.interface_not_implemented": { "es": "'{cls}' no implementa el método '{method}' requerido por '{interface}'", "en": "'{cls}' does not implement method '{method}' required by '{interface}'", }, # ── Structs ─────────────────────────────────────────────────────────────── "struct.undefined": { "es": "el struct '{name}' no está declarado", "en": "struct '{name}' is not declared", }, "struct.field_missing": { "es": "el campo '{field}' no existe en el struct '{struct}'", "en": "field '{field}' does not exist in struct '{struct}'", }, "struct.field_const_reassign": { "es": "el campo const '{field}' en struct '{struct}' no puede reasignarse", "en": "const field '{field}' in struct '{struct}' cannot be reassigned", }, "struct.field_type_mismatch": { "es": "campo '{field}' en struct '{struct}': se esperaba '{expected}', se recibió '{got}'", "en": "field '{field}' in struct '{struct}': expected '{expected}', got '{got}'", }, # ── Async ───────────────────────────────────────────────────────────────── "async.await_outside": { "es": "'await' usado fuera de una función async", "en": "'await' used outside of an async function", }, "async.non_async_call": { "es": "no se puede usar 'await' en la función no-async '{name}'", "en": "cannot use 'await' on non-async function '{name}'", }, # ── Runtime General ─────────────────────────────────────────────────────── "runtime.stack_overflow": { "es": "desbordamiento de pila: demasiadas llamadas anidadas", "en": "stack overflow: too many nested calls", }, "runtime.not_implemented": { "es": "'{feature}' no está implementado aún en este intérprete", "en": "'{feature}' is not yet implemented in this interpreter", }, "runtime.io_error": { "es": "error de I/O: {detail}", "en": "I/O error: {detail}", }, "runtime.invalid_ast": { "es": "el AST recibido no tiene el nodo raíz 'Program'", "en": "received AST does not have the root node 'Program'", }, "runtime.throw_signal": { "es": "excepción lanzada [{cls}]: {msg}", "en": "thrown exception [{cls}]: {msg}", }, } def msg(key: str, **kwargs) -> str: """ Obtiene el mensaje en el idioma activo y aplica los kwargs como formato. Si la clave no existe devuelve el key mismo para no silenciar errores. """ entry = _MESSAGES.get(key) if entry is None: return key template = entry.get(_LANG, entry.get("es", key)) try: return template.format(**kwargs) except KeyError: return template # ────────────────────────────────────────────────────────────────────────────── # Clases Base # ────────────────────────────────────────────────────────────────────────────── class TesseractError(Exception): """ Raíz de todos los errores del lenguaje Tesseract. Preserva línea, columna y función para el traceback. Compatible con InterpreterError existente: hereda de Exception. """ def __init__(self, message: str, file: str = None, line: int = None, col: int = None, function: str = None): self.message = message self.file = file or "" self.line = line self.col = col self.function = function super().__init__(message) def location_str(self) -> str: s = f'File "{self.file}"' if self.line is not None: s += f", line {self.line}" if self.col is not None: s += f", col {self.col}" if self.function: s += f", in {self.function}" return s def __str__(self): return f"{self.location_str()}\n{type(self).__name__}: {self.message}" # Alias de compatibilidad — las partes antiguas del intérprete que atrapan # InterpreterError también atraparán TesseractError si se hace: # InterpreterError = TesseractError (ver sección de migración al final) LangError = TesseractError # ────────────────────────────────────────────────────────────────────────────── # Léxico / Sintaxis # ────────────────────────────────────────────────────────────────────────────── class LexError(TesseractError): """Token inválido o carácter desconocido.""" class SyntaxError(TesseractError): """Estructura sintáctica inválida.""" class IndentError(TesseractError): """Indentación incorrecta (modo bloque).""" class BlockError(TesseractError): """Bloque sin cerrar o llave/palabra huérfana.""" # ────────────────────────────────────────────────────────────────────────────── # Nombres y Scope # ────────────────────────────────────────────────────────────────────────────── class NameError(TesseractError): """Variable o símbolo no definido.""" class RedeclarationError(TesseractError): """Símbolo declarado más de una vez en el mismo scope.""" class ScopeError(TesseractError): """Acceso a símbolo fuera de su scope válido.""" class ImportError(TesseractError): """Módulo o miembro no encontrado al importar.""" # ────────────────────────────────────────────────────────────────────────────── # Tipos # ────────────────────────────────────────────────────────────────────────────── class TypeError(TesseractError): """Error de tipo general.""" class TypeMismatchError(TypeError): """Tipo recibido no coincide con el esperado.""" class TypeInferenceError(TypeError): """El intérprete no pudo inferir el tipo de una expresión.""" class TypeExplicitConflictError(TypeError): """El tipo declarado explícitamente contradice el valor asignado.""" class ReturnTypeError(TypeError): """El valor retornado no coincide con el tipo de retorno declarado.""" class NullTypeError(TypeError): """Se recibió null donde el tipo no lo permite.""" class UnsupportedCastError(TypeError): """Conversión de tipo no soportada.""" # ────────────────────────────────────────────────────────────────────────────── # Variables # ────────────────────────────────────────────────────────────────────────────── class AssignmentError(TesseractError): """Asignación inválida.""" class ConstError(TesseractError): """Reasignación de una constante.""" class UninitializedError(TesseractError): """Uso de variable antes de inicializarla.""" # ────────────────────────────────────────────────────────────────────────────── # Funciones # ────────────────────────────────────────────────────────────────────────────── class FunctionError(TesseractError): """Error general relacionado con funciones.""" class UndefinedFunctionError(FunctionError): """Función llamada que no existe.""" class ArityError(FunctionError): """Número de argumentos incorrecto.""" class ArgumentTypeError(FunctionError): """Tipo de un argumento no coincide con el parámetro.""" class ReturnError(FunctionError): """'return' usado fuera de función o falta de return en función tipada.""" class RecursionLimitError(FunctionError): """Límite de recursión alcanzado.""" # ────────────────────────────────────────────────────────────────────────────── # Operaciones # ────────────────────────────────────────────────────────────────────────────── class OperationError(TesseractError): """Error aritmético o de operador general.""" class InvalidOperandError(OperationError): """Operando inválido para el operador dado.""" class DivisionByZeroError(OperationError): """División o módulo por cero.""" class OverflowError(OperationError): """Resultado fuera del rango del tipo.""" class BitwiseError(OperationError): """Operación bit a bit en tipo no compatible.""" # ────────────────────────────────────────────────────────────────────────────── # Condiciones / Control de Flujo # ────────────────────────────────────────────────────────────────────────────── class ConditionError(TesseractError): """Condición que no evalúa a booleano.""" class BreakOutsideLoopError(TesseractError): """'break' fuera de bucle o switch.""" class ContinueOutsideLoopError(TesseractError): """'continue' fuera de bucle.""" class UnreachableCodeError(TesseractError): """Código inalcanzable detectado.""" # ────────────────────────────────────────────────────────────────────────────── # Colecciones / Indexing # ────────────────────────────────────────────────────────────────────────────── class IndexError(TesseractError): """Índice fuera de rango en lista o tupla.""" def __init__(self, message: str, index=None, length=None, **kwargs): self.index = index self.length = length super().__init__(message, **kwargs) class KeyError(TesseractError): """Clave no encontrada en diccionario.""" def __init__(self, message: str, key=None, **kwargs): self.key = key super().__init__(message, **kwargs) class IterationError(TesseractError): """Intento de iterar sobre un tipo no iterable.""" class CollectionLimitError(TesseractError): """Se excedió el límite de elementos de una colección.""" # ────────────────────────────────────────────────────────────────────────────── # Acceso Profundo arr[0:2:1] # ────────────────────────────────────────────────────────────────────────────── class DeepAccessError(TesseractError): """Base para todos los errores de acceso profundo.""" def __init__(self, message: str, depth: int = None, path: list = None, **kwargs): self.depth = depth # nivel donde falló (0-based) self.path = path or [] # ruta completa ej. [0, 2, 1] super().__init__(message, **kwargs) def path_str(self) -> str: return " → ".join(str(p) for p in self.path) class DeepIndexOutOfRangeError(DeepAccessError): """Índice fuera de rango en algún nivel de la ruta.""" def __init__(self, message: str, depth: int, index, length: int, path: list = None, **kwargs): self.index = index self.length = length super().__init__(message, depth=depth, path=path, **kwargs) class DeepKeyError(DeepAccessError): """Clave inexistente en un dict anidado.""" def __init__(self, message: str, depth: int, key, path: list = None, **kwargs): self.key = key super().__init__(message, depth=depth, path=path, **kwargs) class DeepNotCollectionError(DeepAccessError): """En algún nivel la ruta encontró un valor que no es colección.""" def __init__(self, message: str, depth: int, got_type: str, path: list = None, **kwargs): self.got_type = got_type super().__init__(message, depth=depth, path=path, **kwargs) class DeepNullError(DeepAccessError): """La ruta pasó por un valor null.""" class DeepPathError(DeepAccessError): """La ruta de acceso profundo está mal formada.""" class DeepTypeMismatchError(DeepAccessError): """Se usó índice entero en dict o clave string en lista.""" def __init__(self, message: str, depth: int, expected: str, got: str, path: list = None, **kwargs): self.expected = expected self.got = got super().__init__(message, depth=depth, path=path, **kwargs) # ────────────────────────────────────────────────────────────────────────────── # Rangos # ────────────────────────────────────────────────────────────────────────────── class RangeError(TesseractError): """Base para todos los errores de rango.""" class RangeBoundsError(RangeError): """Inicio mayor que fin.""" def __init__(self, message: str, start=None, end=None, **kwargs): self.start = start self.end = end super().__init__(message, **kwargs) class RangeTypeError(RangeError): """Tipos incompatibles en el rango (int+str, float+str).""" def __init__(self, message: str, left_type: str = None, right_type: str = None, **kwargs): self.left_type = left_type self.right_type = right_type super().__init__(message, **kwargs) class RangeFloatPrecisionError(RangeError): """Float con más de 1 decimal en rango.""" def __init__(self, message: str, value=None, **kwargs): self.value = value super().__init__(message, **kwargs) class RangeUnicodeError(RangeError): """Código unicode inválido o no asignado.""" def __init__(self, message: str, code: str = None, **kwargs): self.code = code super().__init__(message, **kwargs) class RangeUnicodeOrderError(RangeError): """El carácter inicio tiene mayor codepoint unicode que el fin.""" def __init__(self, message: str, start_char: str = None, end_char: str = None, **kwargs): self.start_char = start_char self.end_char = end_char super().__init__(message, **kwargs) class RangeUnicodeCaseMixError(RangeError): """Mezcla de mayúsculas y minúsculas en rango string.""" class RangeNullMixedError(RangeError): """null mezclado con otro tipo en rango (solo null..null es válido).""" class RangeEmptyError(RangeError): """Rango válido pero que produce secuencia vacía.""" # ────────────────────────────────────────────────────────────────────────────── # OOP / Clases # ────────────────────────────────────────────────────────────────────────────── class ClassError(TesseractError): """Error general de clase.""" class AttributeError(ClassError): """Atributo no existe en la instancia o clase.""" class MethodError(ClassError): """Método no encontrado en la jerarquía.""" class InheritanceError(ClassError): """Herencia inválida.""" class InstantiationError(ClassError): """Clase no puede ser instanciada (abstracta, u otra razón).""" class FinalExtensionError(ClassError): """Intento de extender una clase final.""" class InterfaceError(ClassError): """Interfaz no implementada correctamente.""" # ────────────────────────────────────────────────────────────────────────────── # Structs # ────────────────────────────────────────────────────────────────────────────── class StructError(TesseractError): """Error general de struct.""" class StructFieldError(StructError): """Campo inexistente o inaccesible en un struct.""" class StructFieldConstError(StructError): """Reasignación de campo const en un struct.""" class StructFieldTypeError(StructError): """Tipo incorrecto asignado a un campo de struct.""" # ────────────────────────────────────────────────────────────────────────────── # Async # ────────────────────────────────────────────────────────────────────────────── class AsyncError(TesseractError): """Error relacionado con funciones async/await.""" class AwaitOutsideAsyncError(AsyncError): """'await' usado fuera de una función async.""" class NonAsyncAwaitError(AsyncError): """'await' aplicado a una función no-async.""" # ────────────────────────────────────────────────────────────────────────────── # Runtime General # ────────────────────────────────────────────────────────────────────────────── class RuntimeError(TesseractError): """Error de tiempo de ejecución general.""" class StackOverflowError(RuntimeError): """Demasiadas llamadas anidadas.""" class IOError(RuntimeError): """Error de entrada/salida.""" class NotImplementedError(RuntimeError): """Funcionalidad no implementada aún.""" class InvalidASTError(RuntimeError): """El AST recibido tiene estructura inválida.""" # ────────────────────────────────────────────────────────────────────────────── # Helpers de construcción — factories con linecolumn del AST # ────────────────────────────────────────────────────────────────────────────── def _lc(node: dict) -> dict: """ Extrae file/line/col desde el nodo AST. El AST usa el subnodo 'linecolumn': {"line": N, "col": N, "end_line": N} """ lc = node.get("linecolumn", {}) if isinstance(node, dict) else {} return { "line": lc.get("line"), "col": lc.get("col"), } def make_type_mismatch(expected: str, got: str, node: dict, file: str = None, function: str = None) -> TypeMismatchError: return TypeMismatchError( msg("type.mismatch", expected=expected, got=got), file=file, function=function, **_lc(node) ) def make_arity_error(name: str, expected: int, got: int, node: dict, file: str = None, function: str = None) -> ArityError: return ArityError( msg("func.arity", name=name, expected=expected, got=got), file=file, function=function, **_lc(node) ) def make_return_mismatch(name: str, expected: str, got: str, node: dict, file: str = None, function: str = None) -> ReturnTypeError: return ReturnTypeError( msg("func.return_mismatch", name=name, expected=expected, got=got), file=file, function=function, **_lc(node) ) def make_undefined_name(name: str, node: dict, file: str = None, function: str = None) -> NameError: return NameError( msg("name.undefined", name=name), file=file, function=function, **_lc(node) ) def make_const_error(name: str, node: dict, file: str = None, function: str = None) -> ConstError: return ConstError( msg("var.const_reassign", name=name), file=file, function=function, **_lc(node) ) def make_division_zero(node: dict, file: str = None, function: str = None) -> DivisionByZeroError: return DivisionByZeroError( msg("op.division_zero"), file=file, function=function, **_lc(node) ) def make_deep_out_of_range(depth: int, index, length: int, path: list, node: dict, file: str = None, function: str = None) -> DeepIndexOutOfRangeError: return DeepIndexOutOfRangeError( msg("deep.out_of_range", depth=depth, index=index, length=length), depth=depth, index=index, length=length, path=path, file=file, function=function, **_lc(node) ) def make_range_bounds(start, end, node: dict, file: str = None, function: str = None) -> RangeBoundsError: return RangeBoundsError( msg("range.bounds", start=start, end=end), start=start, end=end, file=file, function=function, **_lc(node) ) def make_range_type(left_type: str, right_type: str, node: dict, file: str = None, function: str = None) -> RangeTypeError: return RangeTypeError( msg("range.type_incompatible", left=left_type, right=right_type), left_type=left_type, right_type=right_type, file=file, function=function, **_lc(node) ) def make_range_float_precision(value, node: dict, file: str = None, function: str = None) -> RangeFloatPrecisionError: return RangeFloatPrecisionError( msg("range.float_precision", value=value), value=value, file=file, function=function, **_lc(node) ) def make_range_unicode(code: str, node: dict, file: str = None, function: str = None) -> RangeUnicodeError: return RangeUnicodeError( msg("range.unicode_invalid", code=code), code=code, file=file, function=function, **_lc(node) ) # ────────────────────────────────────────────────────────────────────────────── # resolve_deep — acceso profundo arr[0:2:1] # ────────────────────────────────────────────────────────────────────────────── def resolve_deep(collection, path: list, node: dict, file: str = None, function: str = None): """ Resuelve un acceso profundo tipo arr[0:2:1] donde path = [0, 2, 1]. Lanza la excepción DeepAccessError apropiada si algo falla. """ current = collection for depth, index in enumerate(path): # null if current is None: raise DeepNullError( msg("deep.null_traversal", depth=depth), depth=depth, path=path, file=file, function=function, **_lc(node) ) # debe ser colección if not isinstance(current, (list, tuple, dict)): raise DeepNotCollectionError( msg("deep.not_collection", depth=depth, got=type(current).__name__), depth=depth, got_type=type(current).__name__, path=path, file=file, function=function, **_lc(node) ) # tipo de índice correcto if isinstance(current, (list, tuple)) and not isinstance(index, int): raise DeepTypeMismatchError( msg("deep.type_mismatch", depth=depth, got=type(index).__name__, expected="int"), depth=depth, expected="int", got=type(index).__name__, path=path, file=file, function=function, **_lc(node) ) if isinstance(current, dict) and isinstance(index, int): raise DeepTypeMismatchError( msg("deep.type_mismatch", depth=depth, got="int", expected="key"), depth=depth, expected="key", got="int", path=path, file=file, function=function, **_lc(node) ) # fuera de rango (lista/tupla) if isinstance(current, (list, tuple)): if index < 0 or index >= len(current): raise DeepIndexOutOfRangeError( msg("deep.out_of_range", depth=depth, index=index, length=len(current)), depth=depth, index=index, length=len(current), path=path, file=file, function=function, **_lc(node) ) # clave en dict if isinstance(current, dict) and index not in current: raise DeepKeyError( msg("deep.key_not_found", depth=depth, key=index), depth=depth, key=index, path=path, file=file, function=function, **_lc(node) ) current = current[index] return current # ────────────────────────────────────────────────────────────────────────────── # resolve_unicode — para endpoints de rango string # ────────────────────────────────────────────────────────────────────────────── def resolve_unicode(value: str, node: dict, file: str = None, function: str = None) -> str: """ Convierte 'u06FF' → carácter real. Si el codepoint no existe o no está asignado lanza RangeUnicodeError. """ import re if re.fullmatch(r'u[0-9A-Fa-f]{4,6}', value): try: cp = int(value[1:], 16) char = chr(cp) unicodedata.name(char) # lanza ValueError si no asignado return char except (ValueError, OverflowError): raise make_range_unicode(value, node, file=file, function=function) return value # ────────────────────────────────────────────────────────────────────────────── # validate_range — validación completa de un rango # ────────────────────────────────────────────────────────────────────────────── def validate_range(start, end, node: dict, file: str = None, function: str = None): """ Valida las reglas de rango del lenguaje Tesseract. Lanza la excepción RangeError apropiada si algo viola las reglas. """ st = type(start).__name__ et = type(end).__name__ # null mezclado if (start is None) != (end is None): raise RangeNullMixedError( msg("range.null_mixed"), file=file, function=function, **_lc(node) ) if start is None and end is None: return # null..null es válido # tipos incompatibles _incompatible = { ("int", "str"), ("str", "int"), ("float", "str"), ("str", "float"), } # normalizar: bool -> int a efectos del rango st_n = "int" if st == "bool" else st et_n = "int" if et == "bool" else et if (st_n, et_n) in _incompatible: raise make_range_type(st_n, et_n, node, file=file, function=function) # precisión decimal en float for val in (start, end): if isinstance(val, float): s = str(val) decimals = len(s.split(".")[1]) if "." in s else 0 if decimals > 1: raise make_range_float_precision( val, node, file=file, function=function) # bounds numéricos if isinstance(start, (int, float)) and isinstance(end, (int, float)): if start > end: raise make_range_bounds(start, end, node, file=file, function=function) # rangos string if isinstance(start, str) and isinstance(end, str): sc = ord(start) ec = ord(end) # orden unicode if sc > ec: raise RangeUnicodeOrderError( msg("range.unicode_order", start=start, start_cp=f"{sc:04X}", end=end, end_cp=f"{ec:04X}"), start_char=start, end_char=end, file=file, function=function, **_lc(node) ) # mezcla de case if (start.islower() and end.isupper()) or \ (start.isupper() and end.islower()): raise RangeUnicodeCaseMixError( msg("range.unicode_case_mix", start=start, end=end), file=file, function=function, **_lc(node) ) # ────────────────────────────────────────────────────────────────────────────── # Formatter de Traceback — estilo Python # ────────────────────────────────────────────────────────────────────────────── def format_traceback(error: TesseractError, call_stack: list, source_lines: list = None) -> str: """ Genera un traceback completo al estilo Python con línea de código y cursor. call_stack: lista de tuplas (file, function, line) mantenida por el intérprete en self._call_stack source_lines: lista de líneas del archivo fuente (open(f).readlines()) """ lines = ["Traceback (most recent call last):"] for (c_file, c_func, c_line) in call_stack: lines.append(f' File "{c_file}", line {c_line}, in {c_func}') if source_lines and c_line and 1 <= c_line <= len(source_lines): code_line = source_lines[c_line - 1].rstrip() lines.append(f" {code_line}") # Bloque final — archivo/línea/col del error exacto lines.append(f'\nFile "{error.file}"', ) loc_parts = [] if error.line is not None: loc_parts.append(f"line {error.line}") if error.col is not None: loc_parts.append(f"col {error.col}") if error.function: loc_parts.append(f"in {error.function}") if loc_parts: lines[-1] += ", " + ", ".join(loc_parts) # Mostrar la línea de código exacta con cursor ^^^ if source_lines and error.line and 1 <= error.line <= len(source_lines): code_line = source_lines[error.line - 1].rstrip() lines.append(f" {code_line}") if error.col is not None: # cursor bajo la columna exacta indent = len(code_line) - len(code_line.lstrip()) cursor_pos = max(error.col - 1, indent) lines.append(" " + " " * cursor_pos + "^^^") lines.append(f"{type(error).__name__}: {error.message}") return "\n".join(lines) # ────────────────────────────────────────────────────────────────────────────── # NOTA DE MIGRACIÓN # ────────────────────────────────────────────────────────────────────────────── # Para que el bloque except InterpreterError en interpre.py siga funcionando # sin tocar nada, añade al inicio de interpre.py (después del import): # # from tesseract_errors import TesseractError # InterpreterError = TesseractError # # Todas las subclases de TesseractError serán atrapadas por ese except. # El resto del intérprete no necesita cambios para funcionar. # ──────────────────────────────────────────────────────────────────────────────